@tendrilapp/cli 0.1.40 → 0.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tendril.js CHANGED
@@ -1980,8 +1980,8 @@ var init_src = __esm({
1980
1980
  function variableNameToPath(name) {
1981
1981
  return name.split("/").map(canonicalCssIdentPart).filter((part) => part.length > 0);
1982
1982
  }
1983
- function tokenPathToCssVar(path50) {
1984
- return `--${path50.join("-")}`;
1983
+ function tokenPathToCssVar(path54) {
1984
+ return `--${path54.join("-")}`;
1985
1985
  }
1986
1986
  function toDtcgToken(variable, defaultMode) {
1987
1987
  const modes = Object.keys(variable.valuesByMode);
@@ -2025,11 +2025,11 @@ function toDtcgToken(variable, defaultMode) {
2025
2025
  }
2026
2026
  function mapVariablesToDtcg(variables, defaultMode = "light") {
2027
2027
  const entries = variables.map((variable) => {
2028
- const path50 = variableNameToPath(variable.name);
2029
- if (path50.length === 0) {
2028
+ const path54 = variableNameToPath(variable.name);
2029
+ if (path54.length === 0) {
2030
2030
  throw new DtcgMappingError("empty-name", `Figma variable ${variable.id} has an empty name`);
2031
2031
  }
2032
- return { variable, path: path50 };
2032
+ return { variable, path: path54 };
2033
2033
  });
2034
2034
  const groupPrefixes = /* @__PURE__ */ new Set();
2035
2035
  for (const e of entries) {
@@ -2050,21 +2050,21 @@ function mapVariablesToDtcg(variables, defaultMode = "light") {
2050
2050
  }
2051
2051
  const tokens = {};
2052
2052
  const flat = [];
2053
- for (const { variable, path: path50 } of entries) {
2053
+ for (const { variable, path: path54 } of entries) {
2054
2054
  const token = toDtcgToken(variable, defaultMode);
2055
2055
  let group = tokens;
2056
- for (const segment of path50.slice(0, -1)) {
2056
+ for (const segment of path54.slice(0, -1)) {
2057
2057
  const existing = group[segment];
2058
2058
  group = existing ?? (group[segment] = {});
2059
2059
  }
2060
- const leaf = path50[path50.length - 1];
2060
+ const leaf = path54[path54.length - 1];
2061
2061
  if (group[leaf] !== void 0) {
2062
- throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path50.join(".")}" (variable ${variable.id})`);
2062
+ throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path54.join(".")}" (variable ${variable.id})`);
2063
2063
  }
2064
2064
  group[leaf] = token;
2065
2065
  flat.push({
2066
- path: path50.join("."),
2067
- cssVar: tokenPathToCssVar(path50),
2066
+ path: path54.join("."),
2067
+ cssVar: tokenPathToCssVar(path54),
2068
2068
  type: token.$type,
2069
2069
  value: token.$value
2070
2070
  });
@@ -2253,9 +2253,9 @@ function boundId(value) {
2253
2253
  return isObject(value) && typeof value["boundVariableId"] === "string" ? value["boundVariableId"] : void 0;
2254
2254
  }
2255
2255
  function resolveBinding(ctx, id) {
2256
- const path50 = ctx.pathById.get(id);
2257
- if (path50 === void 0) ctx.unresolved.add(id);
2258
- return path50;
2256
+ const path54 = ctx.pathById.get(id);
2257
+ if (path54 === void 0) ctx.unresolved.add(id);
2258
+ return path54;
2259
2259
  }
2260
2260
  function parseVariantProps(name) {
2261
2261
  if (!name.includes("=")) return void 0;
@@ -2290,8 +2290,8 @@ function walk(ctx, raw) {
2290
2290
  if (!isObject(paint) || paint["visible"] === false) continue;
2291
2291
  const id = boundId(paint);
2292
2292
  if (id !== void 0) {
2293
- const path50 = resolveBinding(ctx, id);
2294
- if (path50 !== void 0) tokens.add(path50);
2293
+ const path54 = resolveBinding(ctx, id);
2294
+ if (path54 !== void 0) tokens.add(path54);
2295
2295
  } else if (typeof paint["color"] === "string") {
2296
2296
  ctx.hardcoded.push({ node: name, property, value: paint["color"] });
2297
2297
  }
@@ -2299,8 +2299,8 @@ function walk(ctx, raw) {
2299
2299
  }
2300
2300
  const radiusId = boundId(raw["cornerRadius"]);
2301
2301
  if (radiusId !== void 0) {
2302
- const path50 = resolveBinding(ctx, radiusId);
2303
- if (path50 !== void 0) tokens.add(path50);
2302
+ const path54 = resolveBinding(ctx, radiusId);
2303
+ if (path54 !== void 0) tokens.add(path54);
2304
2304
  } else if (typeof raw["cornerRadius"] === "number" && raw["cornerRadius"] !== 0) {
2305
2305
  ctx.hardcoded.push({ node: name, property: "border-radius", value: `${raw["cornerRadius"]}px` });
2306
2306
  }
@@ -2310,10 +2310,10 @@ function walk(ctx, raw) {
2310
2310
  layout = { mode: layoutMode === "HORIZONTAL" ? "flex-row" : "flex-column" };
2311
2311
  const gapId = boundId(raw["itemSpacing"]);
2312
2312
  if (gapId !== void 0) {
2313
- const path50 = resolveBinding(ctx, gapId);
2314
- if (path50 !== void 0) {
2315
- layout.gap = path50;
2316
- tokens.add(path50);
2313
+ const path54 = resolveBinding(ctx, gapId);
2314
+ if (path54 !== void 0) {
2315
+ layout.gap = path54;
2316
+ tokens.add(path54);
2317
2317
  }
2318
2318
  } else if (typeof raw["itemSpacing"] === "number" && raw["itemSpacing"] !== 0) {
2319
2319
  ctx.hardcoded.push({ node: name, property: "gap", value: `${raw["itemSpacing"]}px` });
@@ -2322,10 +2322,10 @@ function walk(ctx, raw) {
2322
2322
  for (const field of PADDING_FIELDS) {
2323
2323
  const id = boundId(raw[field]);
2324
2324
  if (id !== void 0) {
2325
- const path50 = resolveBinding(ctx, id);
2326
- if (path50 !== void 0) {
2327
- paddingPaths.push(path50);
2328
- tokens.add(path50);
2325
+ const path54 = resolveBinding(ctx, id);
2326
+ if (path54 !== void 0) {
2327
+ paddingPaths.push(path54);
2328
+ tokens.add(path54);
2329
2329
  }
2330
2330
  } else if (typeof raw[field] === "number" && raw[field] !== 0) {
2331
2331
  ctx.hardcoded.push({ node: name, property: field, value: `${raw[field]}px` });
@@ -2533,9 +2533,17 @@ var init_src3 = __esm({
2533
2533
  });
2534
2534
 
2535
2535
  // packages/cli/src/invocation.ts
2536
- import { existsSync as existsSync5, realpathSync } from "node:fs";
2536
+ import { existsSync as existsSync5, readFileSync as readFileSync5, realpathSync } from "node:fs";
2537
2537
  import path5 from "node:path";
2538
2538
  import { fileURLToPath } from "node:url";
2539
+ function isDevTree(root) {
2540
+ try {
2541
+ const pkg = JSON.parse(readFileSync5(path5.join(root, "package.json"), "utf8"));
2542
+ return pkg.private === true && pkg.name === "@tendrilapp/cli";
2543
+ } catch {
2544
+ return false;
2545
+ }
2546
+ }
2539
2547
  function findPathTendril(pathEnv, platform) {
2540
2548
  const dirs = pathEnv.split(path5.delimiter).filter((d) => d !== "" && !/node_modules[\\/]\.bin/.test(d) && !/[\\/]_npx[\\/]/.test(d));
2541
2549
  const names = platform === "win32" ? ["tendril.cmd", "tendril.bat"] : ["tendril"];
@@ -2557,11 +2565,17 @@ function packageRootOf(file) {
2557
2565
  }
2558
2566
  }
2559
2567
  function resolveInvocation(pathEnv, platform, selfFile = fileURLToPath(import.meta.url)) {
2568
+ let self = null;
2569
+ try {
2570
+ self = packageRootOf(realpathSync(selfFile));
2571
+ } catch {
2572
+ self = null;
2573
+ }
2574
+ if (self !== null && isDevTree(self)) return DEV_INVOCATION;
2560
2575
  const binary = findPathTendril(pathEnv, platform);
2561
2576
  if (binary === null) return NPX_INVOCATION;
2562
2577
  try {
2563
2578
  const onPath = packageRootOf(realpathSync(binary));
2564
- const self = packageRootOf(realpathSync(selfFile));
2565
2579
  if (onPath === null || self === null) return NPX_INVOCATION;
2566
2580
  return realpathSync(onPath) === realpathSync(self) ? "tendril" : NPX_INVOCATION;
2567
2581
  } catch {
@@ -2579,11 +2593,12 @@ function quoteArg(value) {
2579
2593
  if (/["`$]/.test(value)) throw new Error(`refusing to emit a shell argument containing a double quote, backtick or $: ${value}`);
2580
2594
  return /[\s'*?[\]()&;|<>#~]/.test(value) ? `"${value}"` : value;
2581
2595
  }
2582
- var NPX_INVOCATION, cached;
2596
+ var NPX_INVOCATION, DEV_INVOCATION, cached;
2583
2597
  var init_invocation = __esm({
2584
2598
  "packages/cli/src/invocation.ts"() {
2585
2599
  "use strict";
2586
2600
  NPX_INVOCATION = "npx -y -p @tendrilapp/cli@latest tendril";
2601
+ DEV_INVOCATION = "pnpm --filter @tendrilapp/cli dev";
2587
2602
  cached = null;
2588
2603
  }
2589
2604
  });
@@ -4350,7 +4365,7 @@ var init_font_collection = __esm({
4350
4365
  });
4351
4366
 
4352
4367
  // packages/verify/src/font-discovery.ts
4353
- import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync5, realpathSync as realpathSync2 } from "node:fs";
4368
+ import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync2 } from "node:fs";
4354
4369
  import os2 from "node:os";
4355
4370
  import path12 from "node:path";
4356
4371
  function weightFromSubfamily(subfamily) {
@@ -4398,7 +4413,7 @@ function faceAt(bytes, view, dirOffset, file, faceIndex) {
4398
4413
  }
4399
4414
  function facesInFile(file) {
4400
4415
  try {
4401
- const bytes = new Uint8Array(readFileSync5(file));
4416
+ const bytes = new Uint8Array(readFileSync6(file));
4402
4417
  if (bytes.length < 12) return [];
4403
4418
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
4404
4419
  if (isCollection(bytes)) {
@@ -4490,7 +4505,7 @@ var init_font_discovery = __esm({
4490
4505
 
4491
4506
  // packages/verify/src/font-resolve.ts
4492
4507
  import { createHash as createHash2 } from "node:crypto";
4493
- import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
4508
+ import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
4494
4509
  import os3 from "node:os";
4495
4510
  import path13 from "node:path";
4496
4511
  function fontCacheDir() {
@@ -4569,7 +4584,7 @@ async function resolveFonts(family, weights, cacheDir = DEFAULT_FONT_CACHE) {
4569
4584
  }
4570
4585
  }
4571
4586
  const mPath = path13.join(cacheDir, "manifest.json");
4572
- const prior = existsSync9(mPath) ? JSON.parse(readFileSync6(mPath, "utf8")) : [];
4587
+ const prior = existsSync9(mPath) ? JSON.parse(readFileSync7(mPath, "utf8")) : [];
4573
4588
  const portable2 = resolved.map((m) => ({ ...m, file: path13.basename(m.file) }));
4574
4589
  const merged = [...prior.filter((p) => !resolved.some((m) => m.family === p.family && m.weight === p.weight)), ...portable2];
4575
4590
  if (resolved.length > 0) writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
@@ -4583,7 +4598,7 @@ function addLocalFont(family, weight, filePath, cacheDir = DEFAULT_FONT_CACHE, f
4583
4598
  if (![".woff2", ".woff", ".ttf", ".otf", ".ttc"].includes(ext)) {
4584
4599
  throw new Error(`unsupported font file "${ext}" \u2014 use .woff2 (preferred), .woff, .ttf, .otf or .ttc`);
4585
4600
  }
4586
- let bytes = new Uint8Array(readFileSync6(src));
4601
+ let bytes = new Uint8Array(readFileSync7(src));
4587
4602
  if (bytes.length === 0) throw new Error(`font file is empty: ${src}`);
4588
4603
  let storedExt = ext;
4589
4604
  if (isCollection(bytes)) {
@@ -4606,16 +4621,16 @@ ${shown}`
4606
4621
  writeFileSync3(file, bytes);
4607
4622
  const face = { family, weight, source: `${provenance}:${path13.basename(src)}`, sha256, file, license: "unknown" };
4608
4623
  const mPath = path13.join(cacheDir, "manifest.json");
4609
- const prior = existsSync9(mPath) ? JSON.parse(readFileSync6(mPath, "utf8")) : [];
4624
+ const prior = existsSync9(mPath) ? JSON.parse(readFileSync7(mPath, "utf8")) : [];
4610
4625
  const merged = [...prior.filter((p) => !(p.family === family && p.weight === weight)), { ...face, file: path13.basename(file) }];
4611
4626
  writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
4612
4627
  `);
4613
4628
  return face;
4614
4629
  }
4615
4630
  function checkFontLock(lockPath, cacheDir = DEFAULT_FONT_CACHE) {
4616
- const lock = JSON.parse(readFileSync6(lockPath, "utf8"));
4631
+ const lock = JSON.parse(readFileSync7(lockPath, "utf8"));
4617
4632
  const mPath = path13.join(cacheDir, "manifest.json");
4618
- const manifest = existsSync9(mPath) ? JSON.parse(readFileSync6(mPath, "utf8")) : [];
4633
+ const manifest = existsSync9(mPath) ? JSON.parse(readFileSync7(mPath, "utf8")) : [];
4619
4634
  return lock.map((l) => {
4620
4635
  const m = manifest.find((x) => x.family === l.family && x.weight === l.weight);
4621
4636
  if (m === void 0) return { family: l.family, weight: l.weight, status: "missing" };
@@ -4627,7 +4642,7 @@ function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
4627
4642
  if (!existsSync9(mPath)) return [];
4628
4643
  let entries;
4629
4644
  try {
4630
- entries = JSON.parse(readFileSync6(mPath, "utf8"));
4645
+ entries = JSON.parse(readFileSync7(mPath, "utf8"));
4631
4646
  } catch {
4632
4647
  return [];
4633
4648
  }
@@ -4642,7 +4657,7 @@ function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
4642
4657
  }
4643
4658
  function requiredFontsManifest(families, cacheDir = DEFAULT_FONT_CACHE) {
4644
4659
  const mPath = path13.join(cacheDir, "manifest.json");
4645
- const manifest = existsSync9(mPath) ? JSON.parse(readFileSync6(mPath, "utf8")) : [];
4660
+ const manifest = existsSync9(mPath) ? JSON.parse(readFileSync7(mPath, "utf8")) : [];
4646
4661
  const wanted = new Set(families.map((f) => f.toLowerCase()));
4647
4662
  return manifest.filter((f) => wanted.has(f.family.toLowerCase())).map((f) => ({
4648
4663
  family: f.family,
@@ -4659,7 +4674,7 @@ function facesMissingLicense(cacheDir = DEFAULT_FONT_CACHE) {
4659
4674
  if (!existsSync9(mPath)) return [];
4660
4675
  let entries;
4661
4676
  try {
4662
- entries = JSON.parse(readFileSync6(mPath, "utf8"));
4677
+ entries = JSON.parse(readFileSync7(mPath, "utf8"));
4663
4678
  } catch {
4664
4679
  return [];
4665
4680
  }
@@ -4672,7 +4687,7 @@ function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
4672
4687
  if (!existsSync9(mPath)) return [];
4673
4688
  let entries;
4674
4689
  try {
4675
- entries = JSON.parse(readFileSync6(mPath, "utf8"));
4690
+ entries = JSON.parse(readFileSync7(mPath, "utf8"));
4676
4691
  } catch {
4677
4692
  return [];
4678
4693
  }
@@ -4681,7 +4696,7 @@ function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
4681
4696
  if (typeof e.family !== "string" || typeof e.weight !== "number" || typeof e.file !== "string") continue;
4682
4697
  const file = path13.isAbsolute(e.file) && existsSync9(e.file) ? e.file : path13.resolve(cacheDir, path13.basename(e.file));
4683
4698
  if (!existsSync9(file)) continue;
4684
- if (createHash2("sha256").update(readFileSync6(file)).digest("hex") !== e.sha256) continue;
4699
+ if (createHash2("sha256").update(readFileSync7(file)).digest("hex") !== e.sha256) continue;
4685
4700
  const set = byFamily.get(e.family) ?? /* @__PURE__ */ new Set();
4686
4701
  set.add(e.weight);
4687
4702
  byFamily.set(e.family, set);
@@ -4703,7 +4718,7 @@ function addSystemFamily(family, opts = {}) {
4703
4718
  const overwrote = [];
4704
4719
  const cacheDir = opts.cacheDir ?? DEFAULT_FONT_CACHE;
4705
4720
  const manifestFile = path13.join(cacheDir, "manifest.json");
4706
- const prior = existsSync9(manifestFile) ? JSON.parse(readFileSync6(manifestFile, "utf8")) : [];
4721
+ const prior = existsSync9(manifestFile) ? JSON.parse(readFileSync7(manifestFile, "utf8")) : [];
4707
4722
  const taken = /* @__PURE__ */ new Set();
4708
4723
  for (const face of faces) {
4709
4724
  const skip = (reason) => skipped.push({ subfamily: face.subfamily, weight: face.weight, reason });
@@ -4765,17 +4780,17 @@ var init_font_resolve = __esm({
4765
4780
 
4766
4781
  // packages/verify/src/font-faces.ts
4767
4782
  import { createHash as createHash3 } from "node:crypto";
4768
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
4783
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
4769
4784
  import path14 from "node:path";
4770
4785
  function injectedGroups(manifestPath2) {
4771
4786
  if (!existsSync10(manifestPath2)) return { groups: [], shared: false };
4772
- const claimed = JSON.parse(readFileSync7(manifestPath2, "utf8"));
4787
+ const claimed = JSON.parse(readFileSync8(manifestPath2, "utf8"));
4773
4788
  const resolveFile = (f) => path14.isAbsolute(f) && existsSync10(f) ? f : path14.resolve(path14.dirname(manifestPath2), path14.basename(f));
4774
4789
  const byFile = /* @__PURE__ */ new Map();
4775
4790
  for (const f of claimed) {
4776
4791
  const file = resolveFile(f.file);
4777
4792
  if (!existsSync10(file)) continue;
4778
- if (createHash3("sha256").update(readFileSync7(file)).digest("hex") !== f.sha256) continue;
4793
+ if (createHash3("sha256").update(readFileSync8(file)).digest("hex") !== f.sha256) continue;
4779
4794
  const k = `${f.family}:${f.file}`;
4780
4795
  const e = byFile.get(k) ?? { family: f.family, weights: [], file };
4781
4796
  e.weights.push(f.weight);
@@ -4788,7 +4803,7 @@ function fontFaceCss(manifestPath2 = path14.join(fontCacheDir(), "manifest.json"
4788
4803
  const { groups, shared } = injectedGroups(manifestPath2);
4789
4804
  return groups.map((e) => {
4790
4805
  const weight = shared || e.weights.length > 1 ? `${SPAN[0]} ${SPAN[1]}` : String(e.weights[0]);
4791
- return `@font-face { font-family: '${e.family}'; font-weight: ${weight}; font-style: normal; src: url(data:font/woff2;base64,${readFileSync7(e.file).toString("base64")}) format('woff2'); }`;
4806
+ return `@font-face { font-family: '${e.family}'; font-weight: ${weight}; font-style: normal; src: url(data:font/woff2;base64,${readFileSync8(e.file).toString("base64")}) format('woff2'); }`;
4792
4807
  }).join("\n");
4793
4808
  }
4794
4809
  function injectedFamilyWeights(manifestPath2 = path14.join(fontCacheDir(), "manifest.json")) {
@@ -4816,7 +4831,7 @@ var init_font_faces = __esm({
4816
4831
  });
4817
4832
 
4818
4833
  // packages/verify/src/admission.ts
4819
- import { readFileSync as readFileSync8, readdirSync as readdirSync4, existsSync as existsSync11, writeFileSync as writeFileSync4 } from "node:fs";
4834
+ import { readFileSync as readFileSync9, readdirSync as readdirSync4, existsSync as existsSync11, writeFileSync as writeFileSync4 } from "node:fs";
4820
4835
  import path15 from "node:path";
4821
4836
  import { build as build2 } from "esbuild";
4822
4837
  import postcss from "postcss";
@@ -4826,7 +4841,7 @@ function fontWeightsByFamily() {
4826
4841
  const mPath = path15.join(fontCacheDir(), "manifest.json");
4827
4842
  const out = /* @__PURE__ */ new Map();
4828
4843
  if (!existsSync11(mPath)) return out;
4829
- for (const f of JSON.parse(readFileSync8(mPath, "utf8")))
4844
+ for (const f of JSON.parse(readFileSync9(mPath, "utf8")))
4830
4845
  out.set(f.family, [...out.get(f.family) ?? [], f.weight]);
4831
4846
  return out;
4832
4847
  }
@@ -4846,7 +4861,7 @@ var init_admission = __esm({
4846
4861
  });
4847
4862
 
4848
4863
  // packages/verify/src/candidate-css.ts
4849
- import { existsSync as existsSync12, readFileSync as readFileSync9, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
4864
+ import { existsSync as existsSync12, readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
4850
4865
  import path16 from "node:path";
4851
4866
  function candidateCss(bundleDir) {
4852
4867
  const files = ["tokens.css", "styles.css"].map((f) => path16.join(bundleDir, f));
@@ -4865,7 +4880,7 @@ function candidateCss(bundleDir) {
4865
4880
  }
4866
4881
  files.push(path16.join(dir, "tokens.css"), path16.join(dir, "styles.css"));
4867
4882
  }
4868
- return files.filter((f) => existsSync12(f)).map((f) => readFileSync9(f, "utf8")).join("\n");
4883
+ return files.filter((f) => existsSync12(f)).map((f) => readFileSync10(f, "utf8")).join("\n");
4869
4884
  }
4870
4885
  var init_candidate_css = __esm({
4871
4886
  "packages/verify/src/candidate-css.ts"() {
@@ -5083,7 +5098,7 @@ __export(behavior_exports, {
5083
5098
  compileMount: () => compileMount,
5084
5099
  recordingIsDark: () => recordingIsDark
5085
5100
  });
5086
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "node:fs";
5101
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
5087
5102
  import path18 from "node:path";
5088
5103
  import { build as build3 } from "esbuild";
5089
5104
  import { chromium as chromium3 } from "playwright-core";
@@ -5159,8 +5174,8 @@ async function runSteps(page, spec, renderPose) {
5159
5174
  if (renderPose === void 0) return { id: spec.id, pass: false, detail: "commitMatchesPose requires a pose renderer" };
5160
5175
  await settle(page);
5161
5176
  const idle = await shotRoot(page);
5162
- for (const delay of [70, 130, 190]) {
5163
- await page.waitForTimeout(delay);
5177
+ for (const delay2 of [70, 130, 190]) {
5178
+ await page.waitForTimeout(delay2);
5164
5179
  await freezeAnimations(page);
5165
5180
  if (!(await shotRoot(page)).equals(idle)) {
5166
5181
  return { id: spec.id, pass: false, detail: "unmeasurable: the component's pixels change at rest (free-running animation or a timer in YOUR candidate) \u2014 the committed state cannot be observed through it; this IS fixable from code: keep every scored pose reachable with all motion idle (freezable CSS animation, no JS-driven repaints)" };
@@ -5455,7 +5470,7 @@ function recordingIsDark(task) {
5455
5470
  const f = path18.join(task.set, rep, "get_screenshot.json");
5456
5471
  if (!existsSync13(f)) return false;
5457
5472
  try {
5458
- const env = JSON.parse(readFileSync10(f, "utf8")).content.find((c) => c.type === "image");
5473
+ const env = JSON.parse(readFileSync11(f, "utf8")).content.find((c) => c.type === "image");
5459
5474
  if (env?.data === void 0) return false;
5460
5475
  const png = PNG2.sync.read(Buffer.from(env.data, "base64"));
5461
5476
  let sum = 0;
@@ -6071,6 +6086,11 @@ var init_bundle = __esm({
6071
6086
  rep: z9.string(),
6072
6087
  similarity: z9.number(),
6073
6088
  inkRecall: z9.number(),
6089
+ /** Pre-deadband measured values. Declared so the field SURVIVES the
6090
+ * schema round-trip — the emitter writes it (bundle-emit) and zod was
6091
+ * silently stripping it back out, so a reader could not tell a
6092
+ * rounded 0.970 from a measured one. */
6093
+ exact: z9.object({ similarity: z9.number(), inkRecall: z9.number() }).optional(),
6074
6094
  status: ConfigStatusSchema
6075
6095
  });
6076
6096
  BehaviorClaimSchema = z9.object({
@@ -6092,7 +6112,18 @@ var init_bundle = __esm({
6092
6112
  licenseNote: z9.string().optional(),
6093
6113
  /** Content hash over the set manifest + rep envelopes (the identity
6094
6114
  * verify compares against, not the path). */
6095
- hash: z9.string()
6115
+ hash: z9.string(),
6116
+ /** KIT IDENTITY (PROV-1). The Figma file key and component-set node
6117
+ * the recording came from. Both are captured at plan time in the
6118
+ * recording set and were being dropped here, which left a published
6119
+ * bundle unable to say which kit it derives from — while the portal's
6120
+ * consent model is scoped to exactly that file key (ADR-018 §3), and
6121
+ * the right to publish attaches to a kit rather than to a folder
6122
+ * someone named. Optional: recordings planned before this field
6123
+ * existed carry neither, and a set can be a handed-in selection with
6124
+ * no component set at all. Absent means NOT RECORDED, never "no kit". */
6125
+ figmaFile: z9.string().optional(),
6126
+ componentSetNode: z9.string().optional()
6096
6127
  }),
6097
6128
  environment: z9.object({
6098
6129
  chrome: z9.string(),
@@ -6141,6 +6172,198 @@ var init_bundle = __esm({
6141
6172
  }
6142
6173
  });
6143
6174
 
6175
+ // packages/metadata/src/published-surface.ts
6176
+ function classifyBundleSurface(files, opts) {
6177
+ const published = [];
6178
+ const excluded = [];
6179
+ const unknown = [];
6180
+ for (const raw of files) {
6181
+ const path54 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
6182
+ const inEvidence = path54.startsWith(`${EVIDENCE_DIR}/`);
6183
+ const name = inEvidence ? path54.slice(EVIDENCE_DIR.length + 1) : path54;
6184
+ if (name.includes("/")) {
6185
+ unknown.push(path54);
6186
+ continue;
6187
+ }
6188
+ if (inEvidence) {
6189
+ if (name === "verify-report.json") published.push({ path: path54, role: "verify-report" });
6190
+ else if (name === "diff-legend.txt") published.push({ path: path54, role: "diff-legend" });
6191
+ else if (name === "inspect.html") published.push({ path: path54, role: "inspect-sheet" });
6192
+ else {
6193
+ const hit = EVIDENCE_PATTERNS.find((p) => p.re.test(name));
6194
+ if (hit !== void 0) published.push({ path: path54, role: hit.role });
6195
+ else unknown.push(path54);
6196
+ }
6197
+ continue;
6198
+ }
6199
+ if (name === opts.entry) published.push({ path: path54, role: "entry" });
6200
+ else if (name === "styles.css") published.push({ path: path54, role: "styles" });
6201
+ else if (name === "tokens.css") published.push({ path: path54, role: "tokens" });
6202
+ else if (name === "fonts.css") published.push({ path: path54, role: "fonts" });
6203
+ else if (name === "component.json") published.push({ path: path54, role: "manifest" });
6204
+ else {
6205
+ const skip = EXCLUDED.find((e) => e.test(name));
6206
+ if (skip !== void 0) excluded.push({ path: path54, reason: skip.reason });
6207
+ else unknown.push(path54);
6208
+ }
6209
+ }
6210
+ const roles = new Set(published.map((p) => p.role));
6211
+ return { published, excluded, unknown, missingRequired: REQUIRED_ROLES.filter((r) => !roles.has(r)) };
6212
+ }
6213
+ function missingInspectCrops(sheetText, publishedPaths) {
6214
+ const held = new Set(publishedPaths);
6215
+ const missing = /* @__PURE__ */ new Set();
6216
+ for (const [, name] of sheetText.matchAll(/src="\.\/([^"]+)"/g)) {
6217
+ const path54 = `${EVIDENCE_DIR}/${name}`;
6218
+ if (!held.has(path54)) missing.add(path54);
6219
+ }
6220
+ return [...missing].sort();
6221
+ }
6222
+ var EVIDENCE_DIR, REQUIRED_ROLES, EXCLUDED, EVIDENCE_PATTERNS, SCORED_FILE_ROLES;
6223
+ var init_published_surface = __esm({
6224
+ "packages/metadata/src/published-surface.ts"() {
6225
+ "use strict";
6226
+ EVIDENCE_DIR = "verify-evidence";
6227
+ REQUIRED_ROLES = ["entry", "styles", "manifest", "verify-report", "evidence-triple"];
6228
+ EXCLUDED = [
6229
+ { test: (n) => n === "score-history.jsonl", reason: "generation loop score history" },
6230
+ { test: (n) => n === "loop-state.json", reason: "generation loop state" },
6231
+ { test: (n) => n === "run-log.json", reason: "generation run log" },
6232
+ { test: (n) => n.endsWith(".md"), reason: "brief / debrief / model response" },
6233
+ { test: (n) => n === ".DS_Store", reason: "macOS directory metadata" }
6234
+ ];
6235
+ EVIDENCE_PATTERNS = [
6236
+ { re: /^.+-absent-\d+-(?:ref|render)\.png$/, role: "evidence-absent-crop" },
6237
+ { re: /^.+-inspect-\d+-(?:ref|render)\.png$/, role: "evidence-zoom-crop" },
6238
+ { re: /^.+-(?:ref|render|diff)\.png$/, role: "evidence-triple" }
6239
+ ];
6240
+ SCORED_FILE_ROLES = /* @__PURE__ */ new Set([
6241
+ "entry",
6242
+ "styles",
6243
+ "tokens",
6244
+ "fonts",
6245
+ "manifest",
6246
+ "evidence-triple",
6247
+ "evidence-absent-crop",
6248
+ "diff-legend"
6249
+ ]);
6250
+ }
6251
+ });
6252
+
6253
+ // packages/metadata/src/bundle-files.ts
6254
+ import { createHash as createHash4 } from "node:crypto";
6255
+ import { readFileSync as readFileSync12, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
6256
+ import path19 from "node:path";
6257
+ function bundleFiles(dir, prefix = "") {
6258
+ const out = [];
6259
+ for (const entry of readdirSync6(dir).sort()) {
6260
+ const full = path19.join(dir, entry);
6261
+ const rel = prefix === "" ? entry : `${prefix}/${entry}`;
6262
+ if (statSync3(full).isDirectory()) out.push(...bundleFiles(full, rel));
6263
+ else out.push(rel);
6264
+ }
6265
+ return out;
6266
+ }
6267
+ function digestScoredFiles(bundleDir, entry) {
6268
+ const surface = classifyBundleSurface(bundleFiles(bundleDir), { entry });
6269
+ const out = {};
6270
+ for (const file of surface.published) {
6271
+ if (!SCORED_FILE_ROLES.has(file.role)) continue;
6272
+ out[file.path] = createHash4("sha256").update(readFileSync12(path19.join(bundleDir, file.path))).digest("hex");
6273
+ }
6274
+ return out;
6275
+ }
6276
+ var init_bundle_files = __esm({
6277
+ "packages/metadata/src/bundle-files.ts"() {
6278
+ "use strict";
6279
+ init_published_surface();
6280
+ }
6281
+ });
6282
+
6283
+ // packages/metadata/src/verify-report.ts
6284
+ function verifyReportForTransport(report, bundleName) {
6285
+ const { dir: _dir, ...evidence } = report.evidence;
6286
+ const { eyeCheck: _eyeCheck, ...rest } = report;
6287
+ return { ...rest, bundle: bundleName, evidence };
6288
+ }
6289
+ function nonEmptyArray(value) {
6290
+ return Array.isArray(value) && value.length > 0;
6291
+ }
6292
+ function undisclosedTrustFacts(report) {
6293
+ if (report === null || typeof report !== "object" || Array.isArray(report)) return [];
6294
+ const r = report;
6295
+ const facts = [];
6296
+ if (r["recordingSetDrift"] !== void 0 && r["recordingSetDrift"] !== null) {
6297
+ facts.push({
6298
+ pointer: "$.recordingSetDrift",
6299
+ consequence: "the recording set that was scored is not the one the bundle stamps, so these scores describe a different design than the bundle claims"
6300
+ });
6301
+ }
6302
+ if (nonEmptyArray(r["substitutedFamilies"])) {
6303
+ facts.push({
6304
+ pointer: "$.substitutedFamilies",
6305
+ consequence: "a font family was substituted, so the scores measure a substitute face and no config can be certified under one"
6306
+ });
6307
+ }
6308
+ if (nonEmptyArray(r["certBlockedByAbsentInk"])) {
6309
+ facts.push({
6310
+ pointer: "$.certBlockedByAbsentInk",
6311
+ consequence: "certification was blocked by absent ink, which a verdict alone does not say"
6312
+ });
6313
+ }
6314
+ return facts;
6315
+ }
6316
+ function isSetHash(value) {
6317
+ return typeof value === "string" && /^[0-9a-f]{64}$/.test(value);
6318
+ }
6319
+ function readScoredFiles(report) {
6320
+ if (report === null || typeof report !== "object" || Array.isArray(report)) return void 0;
6321
+ const value = report["scoredFiles"];
6322
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
6323
+ const entries = Object.entries(value);
6324
+ if (entries.length === 0) return void 0;
6325
+ const out = {};
6326
+ for (const [path54, digest] of entries) {
6327
+ if (path54 === "" || path54.startsWith("/") || path54.includes("..")) return void 0;
6328
+ if (!isSetHash(digest)) return void 0;
6329
+ out[path54] = digest;
6330
+ }
6331
+ return out;
6332
+ }
6333
+ function compareScoredFiles(recorded, actual) {
6334
+ const missing = [];
6335
+ const unscored = [];
6336
+ const changed = [];
6337
+ for (const [path54, digest] of Object.entries(recorded)) {
6338
+ if (!(path54 in actual)) missing.push(path54);
6339
+ else if (actual[path54] !== digest) changed.push(path54);
6340
+ }
6341
+ for (const path54 of Object.keys(actual)) if (!(path54 in recorded)) unscored.push(path54);
6342
+ return { missing: missing.sort(), unscored: unscored.sort(), changed: changed.sort() };
6343
+ }
6344
+ function scoredRecordingSetHash(report) {
6345
+ if (report === null || typeof report !== "object" || Array.isArray(report)) return void 0;
6346
+ const r = report;
6347
+ const drift = r["recordingSetDrift"];
6348
+ if (drift !== null && typeof drift === "object" && !Array.isArray(drift)) {
6349
+ const current = drift["current"];
6350
+ if (isSetHash(current)) return current;
6351
+ }
6352
+ const manifest = r["bundleManifest"];
6353
+ if (manifest !== null && typeof manifest === "object" && !Array.isArray(manifest)) {
6354
+ const stamped = manifest["recordingSetHash"];
6355
+ if (isSetHash(stamped)) return stamped;
6356
+ }
6357
+ return void 0;
6358
+ }
6359
+ var VERIFY_REPORT_FILENAME;
6360
+ var init_verify_report = __esm({
6361
+ "packages/metadata/src/verify-report.ts"() {
6362
+ "use strict";
6363
+ VERIFY_REPORT_FILENAME = "verify-report.json";
6364
+ }
6365
+ });
6366
+
6144
6367
  // packages/metadata/src/motion-css.ts
6145
6368
  var MOTION_CSS_PATTERN, scannableCss, MOTION_TOKEN_NAMES;
6146
6369
  var init_motion_css = __esm({
@@ -6366,14 +6589,17 @@ var init_src4 = __esm({
6366
6589
  init_extract();
6367
6590
  init_recording_set();
6368
6591
  init_bundle();
6592
+ init_bundle_files();
6593
+ init_verify_report();
6594
+ init_published_surface();
6369
6595
  init_motion_css();
6370
6596
  init_profile();
6371
6597
  }
6372
6598
  });
6373
6599
 
6374
6600
  // packages/verify/src/bundle-quality.ts
6375
- import { readFileSync as readFileSync11, readdirSync as readdirSync6, rmSync as rmSync2, writeFileSync as writeFileSync5, existsSync as existsSync14 } from "node:fs";
6376
- import path19 from "node:path";
6601
+ import { readFileSync as readFileSync13, readdirSync as readdirSync7, rmSync as rmSync2, writeFileSync as writeFileSync5, existsSync as existsSync14 } from "node:fs";
6602
+ import path20 from "node:path";
6377
6603
  function unscopedReduceBlockFindings(file, css) {
6378
6604
  const stripped = css.replace(/\/\*[\s\S]*?\*\//g, (c) => c.replace(/[^\n]/g, " "));
6379
6605
  const media = /@media[^{]*prefers-reduced-motion\s*:\s*reduce[^{]*\{/.exec(stripped);
@@ -6431,16 +6657,16 @@ function recordedAssetFindings(entryFile, entrySource, recordedPaths) {
6431
6657
  function recordedAssetPaths(setDir, reps) {
6432
6658
  const map = /* @__PURE__ */ new Map();
6433
6659
  for (const rep of reps) {
6434
- const dir = path19.join(setDir, rep);
6660
+ const dir = path20.join(setDir, rep);
6435
6661
  let files;
6436
6662
  try {
6437
- files = readdirSync6(dir).filter((f) => f.startsWith("asset-") && f.endsWith(".svg"));
6663
+ files = readdirSync7(dir).filter((f) => f.startsWith("asset-") && f.endsWith(".svg"));
6438
6664
  } catch {
6439
6665
  continue;
6440
6666
  }
6441
6667
  for (const f of files) {
6442
6668
  try {
6443
- const svg = readFileSync11(path19.join(dir, f), "utf8");
6669
+ const svg = readFileSync13(path20.join(dir, f), "utf8");
6444
6670
  for (const m of svg.matchAll(/\sd="([^"]+)"/g)) {
6445
6671
  const d = m[1];
6446
6672
  const owners = map.get(d) ?? [];
@@ -6508,17 +6734,17 @@ function recordedTokenMapState(setDir, reps) {
6508
6734
  const readMap = (file) => {
6509
6735
  if (!existsSync14(file)) return void 0;
6510
6736
  try {
6511
- const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync11(file, "utf8"))) || "{}");
6737
+ const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync13(file, "utf8"))) || "{}");
6512
6738
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
6513
6739
  } catch {
6514
6740
  return {};
6515
6741
  }
6516
6742
  };
6517
- const setLevel = readMap(path19.join(setDir, "get_variable_defs.json"));
6743
+ const setLevel = readMap(path20.join(setDir, "get_variable_defs.json"));
6518
6744
  if (setLevel !== void 0) return Object.keys(setLevel).length === 0 ? "empty" : "populated";
6519
6745
  let recorded = false;
6520
6746
  for (const rep of reps) {
6521
- const m = readMap(path19.join(setDir, rep, "get_variable_defs.json"));
6747
+ const m = readMap(path20.join(setDir, rep, "get_variable_defs.json"));
6522
6748
  if (m === void 0) continue;
6523
6749
  recorded = true;
6524
6750
  if (Object.keys(m).length > 0) return "populated";
@@ -6530,7 +6756,7 @@ function recordedTokensByValue(setDir, reps) {
6530
6756
  const readMap = (file) => {
6531
6757
  if (!existsSync14(file)) return void 0;
6532
6758
  try {
6533
- const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync11(file, "utf8"))) || "{}");
6759
+ const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync13(file, "utf8"))) || "{}");
6534
6760
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
6535
6761
  } catch {
6536
6762
  return void 0;
@@ -6548,8 +6774,8 @@ function recordedTokensByValue(setDir, reps) {
6548
6774
  byValue.set(key, candidates);
6549
6775
  }
6550
6776
  };
6551
- absorb(readMap(path19.join(setDir, "get_variable_defs.json")));
6552
- for (const rep of reps) absorb(readMap(path19.join(setDir, rep, "get_variable_defs.json")));
6777
+ absorb(readMap(path20.join(setDir, "get_variable_defs.json")));
6778
+ for (const rep of reps) absorb(readMap(path20.join(setDir, rep, "get_variable_defs.json")));
6553
6779
  return byValue;
6554
6780
  }
6555
6781
  function scannable(css) {
@@ -6658,11 +6884,11 @@ function conventionsFindings(entrySource, css, profile) {
6658
6884
  }
6659
6885
  async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, contract, profile) {
6660
6886
  const findings = [];
6661
- const entryPath = path19.join(bundleDir, entry);
6662
- const cssPath = path19.join(bundleDir, "styles.css");
6663
- const tokensPath = path19.join(bundleDir, "tokens.css");
6664
- const css = existsSync14(cssPath) ? readFileSync11(cssPath, "utf8") : "";
6665
- const tokensCss = existsSync14(tokensPath) ? readFileSync11(tokensPath, "utf8") : void 0;
6887
+ const entryPath = path20.join(bundleDir, entry);
6888
+ const cssPath = path20.join(bundleDir, "styles.css");
6889
+ const tokensPath = path20.join(bundleDir, "tokens.css");
6890
+ const css = existsSync14(cssPath) ? readFileSync13(cssPath, "utf8") : "";
6891
+ const tokensCss = existsSync14(tokensPath) ? readFileSync13(tokensPath, "utf8") : void 0;
6666
6892
  findings.push(
6667
6893
  ...fontStackFindings(
6668
6894
  [
@@ -6675,8 +6901,8 @@ async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, c
6675
6901
  if (existsSync14(entryPath)) {
6676
6902
  const workDir = newScratchDir("quality");
6677
6903
  try {
6678
- const tsxPath = path19.join(workDir, entry);
6679
- writeFileSync5(tsxPath, readFileSync11(entryPath, "utf8"));
6904
+ const tsxPath = path20.join(workDir, entry);
6905
+ writeFileSync5(tsxPath, readFileSync13(entryPath, "utf8"));
6680
6906
  for (const d of runTscStrict([tsxPath]).diagnostics) {
6681
6907
  if (d.code === 2307 && /['"]\.\/composed\//.test(d.message)) continue;
6682
6908
  findings.push({ kind: "tsc", file: entry, ...d.line === void 0 ? {} : { line: d.line }, message: `TS${d.code}: ${d.message}` });
@@ -6686,7 +6912,7 @@ async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, c
6686
6912
  }
6687
6913
  }
6688
6914
  if (contract !== void 0 && existsSync14(entryPath)) {
6689
- findings.push(...expertLensFindings(entry, readFileSync11(entryPath, "utf8"), contract));
6915
+ findings.push(...expertLensFindings(entry, readFileSync13(entryPath, "utf8"), contract));
6690
6916
  }
6691
6917
  for (const sheet of [
6692
6918
  { file: "styles.css", text: css },
@@ -6697,7 +6923,7 @@ async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, c
6697
6923
  findings.push(...placeholderSelectorFindings(sheet.file, sheet.text));
6698
6924
  }
6699
6925
  if (set !== void 0 && existsSync14(entryPath)) {
6700
- findings.push(...recordedAssetFindings(entry, readFileSync11(entryPath, "utf8"), recordedAssetPaths(set.dir, set.reps)));
6926
+ findings.push(...recordedAssetFindings(entry, readFileSync13(entryPath, "utf8"), recordedAssetPaths(set.dir, set.reps)));
6701
6927
  }
6702
6928
  if (css !== "") {
6703
6929
  const mapState = set === void 0 ? void 0 : recordedTokenMapState(set.dir, set.reps);
@@ -6719,7 +6945,7 @@ async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, c
6719
6945
  })
6720
6946
  );
6721
6947
  if (profile !== void 0 && existsSync14(entryPath)) {
6722
- findings.push(...conventionsFindings(readFileSync11(entryPath, "utf8"), css, profile));
6948
+ findings.push(...conventionsFindings(readFileSync13(entryPath, "utf8"), css, profile));
6723
6949
  }
6724
6950
  return { findings, tokensAbsent: tokensCss === void 0 && !/var\(\s*--/.test(css) };
6725
6951
  }
@@ -6820,8 +7046,8 @@ var init_effect_geometry = __esm({
6820
7046
  });
6821
7047
 
6822
7048
  // packages/verify/src/bundle-score.ts
6823
- import { existsSync as existsSync15, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
6824
- import path20 from "node:path";
7049
+ import { existsSync as existsSync15, mkdirSync as mkdirSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "node:fs";
7050
+ import path21 from "node:path";
6825
7051
  import { build as build4 } from "esbuild";
6826
7052
  import { chromium as chromium4 } from "playwright-core";
6827
7053
  function getFontFaces2() {
@@ -6875,7 +7101,7 @@ function chooseSeat(cropAt, ref, padW, padH, backdrop, origin, extents) {
6875
7101
  }
6876
7102
  function metadataRoot(set, rep) {
6877
7103
  try {
6878
- const text = JSON.parse(readFileSync12(path20.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
7104
+ const text = JSON.parse(readFileSync14(path21.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
6879
7105
  return parseMetadataStructure(text);
6880
7106
  } catch {
6881
7107
  return void 0;
@@ -6930,19 +7156,19 @@ function smallSemanticNodes(set, rep, maxArea = 1024) {
6930
7156
  });
6931
7157
  }
6932
7158
  function repMeta(set, rep) {
6933
- const text = JSON.parse(readFileSync12(path20.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
7159
+ const text = JSON.parse(readFileSync14(path21.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
6934
7160
  const root = parseMetadataStructure(text);
6935
7161
  return { w: Math.round(root.width ?? 100), h: Math.round(root.height ?? 40) };
6936
7162
  }
6937
7163
  function repRef(set, rep) {
6938
- const env = JSON.parse(readFileSync12(path20.join(set, rep, "get_screenshot.json"), "utf8")).content.find((c) => c.type === "image");
7164
+ const env = JSON.parse(readFileSync14(path21.join(set, rep, "get_screenshot.json"), "utf8")).content.find((c) => c.type === "image");
6939
7165
  return Uint8Array.from(Buffer.from(env?.data ?? "", "base64"));
6940
7166
  }
6941
7167
  function repEffectExtents(set, rep) {
6942
- const file = path20.join(set, rep, "get_design_context.json");
7168
+ const file = path21.join(set, rep, "get_design_context.json");
6943
7169
  if (!existsSync15(file)) return void 0;
6944
7170
  try {
6945
- const text = JSON.parse(readFileSync12(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
7171
+ const text = JSON.parse(readFileSync14(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
6946
7172
  const extents = shadowExtents(text);
6947
7173
  return extents.top + extents.right + extents.bottom + extents.left > 0 ? extents : void 0;
6948
7174
  } catch {
@@ -6952,17 +7178,17 @@ function repEffectExtents(set, rep) {
6952
7178
  async function scoreBundleForTask(task, bundleDir, bar = BAR, opts = {}) {
6953
7179
  if (opts.evidenceDir !== void 0) {
6954
7180
  mkdirSync3(opts.evidenceDir, { recursive: true });
6955
- writeFileSync6(path20.join(opts.evidenceDir, "diff-legend.txt"), DIFF_LEGEND_TEXT);
7181
+ writeFileSync6(path21.join(opts.evidenceDir, "diff-legend.txt"), DIFF_LEGEND_TEXT);
6956
7182
  }
6957
7183
  const CONFIGS2 = task.configs;
6958
7184
  const timeoutMs = opts.mountDeadlineMs ?? ENGINE_MOUNT_DEADLINE_MS;
6959
- const entryTsx = path20.join(bundleDir, task.entry);
7185
+ const entryTsx = path21.join(bundleDir, task.entry);
6960
7186
  if (!existsSync15(entryTsx)) return CONFIGS2.map((c) => ({ rep: c.rep, similarity: 0, inkRecall: 0, exact: { similarity: 0, inkRecall: 0 }, pass: false, error: `${task.entry} missing` }));
6961
7187
  const css = candidateCss(bundleDir);
6962
7188
  const mountSrc = `
6963
7189
  import { createElement } from "react";
6964
7190
  import { createRoot } from "react-dom/client";
6965
- import * as B from ${JSON.stringify(path20.resolve(entryTsx))};
7191
+ import * as B from ${JSON.stringify(path21.resolve(entryTsx))};
6966
7192
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
6967
7193
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
6968
7194
  const root = document.getElementById("root");
@@ -7061,12 +7287,12 @@ body{margin:0;background:rgb(${backdrop.join(",")})}
7061
7287
  return name === void 0 ? c : { ...c, name };
7062
7288
  });
7063
7289
  if (opts.evidenceDir !== void 0) {
7064
- writeFileSync6(path20.join(opts.evidenceDir, `${cfg.rep}-render.png`), scored);
7065
- writeFileSync6(path20.join(opts.evidenceDir, `${cfg.rep}-ref.png`), ref);
7066
- writeFileSync6(path20.join(opts.evidenceDir, `${cfg.rep}-diff.png`), diffPng(scored, ref, [], backdrop));
7290
+ writeFileSync6(path21.join(opts.evidenceDir, `${cfg.rep}-render.png`), scored);
7291
+ writeFileSync6(path21.join(opts.evidenceDir, `${cfg.rep}-ref.png`), ref);
7292
+ writeFileSync6(path21.join(opts.evidenceDir, `${cfg.rep}-diff.png`), diffPng(scored, ref, [], backdrop));
7067
7293
  for (const [i, c] of absent.slice(0, 3).entries()) {
7068
- writeFileSync6(path20.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-ref.png`), zoomCrop(ref, c));
7069
- writeFileSync6(path20.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-render.png`), zoomCrop(scored, c));
7294
+ writeFileSync6(path21.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-ref.png`), zoomCrop(ref, c));
7295
+ writeFileSync6(path21.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-render.png`), zoomCrop(scored, c));
7070
7296
  }
7071
7297
  }
7072
7298
  return {
@@ -7313,7 +7539,7 @@ var init_parity = __esm({
7313
7539
  // packages/verify/src/composition.ts
7314
7540
  import { createRequire as createRequire2 } from "node:module";
7315
7541
  import { existsSync as existsSync16 } from "node:fs";
7316
- import path21 from "node:path";
7542
+ import path22 from "node:path";
7317
7543
  import { build as build6 } from "esbuild";
7318
7544
  import { chromium as chromium7 } from "playwright-core";
7319
7545
  function getFontFaces4() {
@@ -7321,9 +7547,9 @@ function getFontFaces4() {
7321
7547
  return _fontFaces4;
7322
7548
  }
7323
7549
  async function compileInstrumentedMount(task, bundleDir, composedParts = []) {
7324
- const entryTsx = path21.join(bundleDir, task.entry);
7550
+ const entryTsx = path22.join(bundleDir, task.entry);
7325
7551
  if (!existsSync16(entryTsx)) return { error: `${task.entry} missing` };
7326
- const requireFromVerify = createRequire2(path21.join(VERIFY_PKG_DIR, "package.json"));
7552
+ const requireFromVerify = createRequire2(path22.join(VERIFY_PKG_DIR, "package.json"));
7327
7553
  let realJsxPath;
7328
7554
  try {
7329
7555
  realJsxPath = requireFromVerify.resolve("react/jsx-runtime");
@@ -7334,8 +7560,8 @@ async function compileInstrumentedMount(task, bundleDir, composedParts = []) {
7334
7560
  import { createElement } from "react";
7335
7561
  import { createRoot } from "react-dom/client";
7336
7562
  import { __registerParts } from "react/jsx-runtime";
7337
- import * as B from ${JSON.stringify(path21.resolve(entryTsx))};
7338
- ${composedParts.map((p, i) => `import * as CP${i} from ${JSON.stringify(path21.resolve(bundleDir, p.modulePath))};`).join("\n")}
7563
+ import * as B from ${JSON.stringify(path22.resolve(entryTsx))};
7564
+ ${composedParts.map((p, i) => `import * as CP${i} from ${JSON.stringify(path22.resolve(bundleDir, p.modulePath))};`).join("\n")}
7339
7565
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; partComponents: string[] } }).__cfg;
7340
7566
  const pairs: Array<[unknown, string]> = [];
7341
7567
  for (const name of cfg.partComponents) {
@@ -7391,7 +7617,7 @@ function expectedParts(task, roles, mainSlug) {
7391
7617
  }
7392
7618
  function interiorRegions(setDir, roles) {
7393
7619
  const mains = roles.main;
7394
- const withInterior = mains.filter((m) => existsSync16(path21.join(setDir, m, "get_metadata_interior.json")));
7620
+ const withInterior = mains.filter((m) => existsSync16(path22.join(setDir, m, "get_metadata_interior.json")));
7395
7621
  if (withInterior.length === 0) {
7396
7622
  return { unavailable: "no interior geometry recorded for any main (pre-obligation set) \u2014 re-record with `tendril record` to enable crop checks" };
7397
7623
  }
@@ -7665,16 +7891,16 @@ export const jsxs = (t, p, k) => real.jsxs(t, inject(t, p), k);
7665
7891
 
7666
7892
  // packages/verify/src/occlusion.ts
7667
7893
  import { existsSync as existsSync17 } from "node:fs";
7668
- import path22 from "node:path";
7894
+ import path23 from "node:path";
7669
7895
  import { build as build7 } from "esbuild";
7670
7896
  import { chromium as chromium8 } from "playwright-core";
7671
7897
  async function compileTwoUp(task, bundleDir) {
7672
- const entryTsx = path22.join(bundleDir, task.entry);
7898
+ const entryTsx = path23.join(bundleDir, task.entry);
7673
7899
  if (!existsSync17(entryTsx)) return { error: `${task.entry} missing` };
7674
7900
  const src = `
7675
7901
  import { createElement } from "react";
7676
7902
  import { createRoot } from "react-dom/client";
7677
- import * as B from ${JSON.stringify(path22.resolve(entryTsx))};
7903
+ import * as B from ${JSON.stringify(path23.resolve(entryTsx))};
7678
7904
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
7679
7905
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
7680
7906
  for (const id of ["first", "second"]) {
@@ -7925,26 +8151,26 @@ var init_src5 = __esm({
7925
8151
  });
7926
8152
 
7927
8153
  // packages/cli/src/environment.ts
7928
- import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
7929
- import path23 from "node:path";
7930
- import { createHash as createHash4 } from "node:crypto";
8154
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "node:fs";
8155
+ import path24 from "node:path";
8156
+ import { createHash as createHash5 } from "node:crypto";
7931
8157
  import { fileURLToPath as fileURLToPath5 } from "node:url";
7932
8158
  function cliVersion() {
7933
8159
  try {
7934
- return JSON.parse(readFileSync14(path23.join(path23.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
8160
+ return JSON.parse(readFileSync16(path24.join(path24.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
7935
8161
  } catch {
7936
8162
  return "dev";
7937
8163
  }
7938
8164
  }
7939
8165
  function environmentStamp(taskFamilies) {
7940
- const manifestPath2 = path23.join(fontCacheDir(), "manifest.json");
8166
+ const manifestPath2 = path24.join(fontCacheDir(), "manifest.json");
7941
8167
  let fontsHash = null;
7942
8168
  if (existsSync18(manifestPath2)) {
7943
8169
  try {
7944
- const entries = JSON.parse(readFileSync14(manifestPath2, "utf8"));
8170
+ const entries = JSON.parse(readFileSync16(manifestPath2, "utf8"));
7945
8171
  const scoped = taskFamilies === void 0 || taskFamilies === null ? entries : entries.filter((f) => taskFamilies.some((fam) => fam.toLowerCase() === f.family.toLowerCase()));
7946
8172
  const faces = scoped.map((f) => `${f.family}:${f.weight}:${f.sha256}`).sort();
7947
- fontsHash = faces.length === 0 ? null : createHash4("sha256").update(faces.join("\n")).digest("hex").slice(0, 16);
8173
+ fontsHash = faces.length === 0 ? null : createHash5("sha256").update(faces.join("\n")).digest("hex").slice(0, 16);
7948
8174
  } catch {
7949
8175
  fontsHash = null;
7950
8176
  }
@@ -7985,8 +8211,8 @@ var init_describe = __esm({
7985
8211
  });
7986
8212
 
7987
8213
  // packages/cli/src/env.ts
7988
- import { existsSync as existsSync19, readFileSync as readFileSync15 } from "node:fs";
7989
- import path24 from "node:path";
8214
+ import { existsSync as existsSync19, readFileSync as readFileSync17 } from "node:fs";
8215
+ import path25 from "node:path";
7990
8216
  function parseEnv(content) {
7991
8217
  const entries = /* @__PURE__ */ new Map();
7992
8218
  for (const line of content.split("\n")) {
@@ -7998,9 +8224,9 @@ function parseEnv(content) {
7998
8224
  function resolveCredential(name) {
7999
8225
  const fromProcess = process.env[name];
8000
8226
  if (fromProcess) return fromProcess;
8001
- const envPath = path24.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
8227
+ const envPath = path25.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
8002
8228
  if (!existsSync19(envPath)) return void 0;
8003
- return parseEnv(readFileSync15(envPath, "utf8")).get(name);
8229
+ return parseEnv(readFileSync17(envPath, "utf8")).get(name);
8004
8230
  }
8005
8231
  var init_env = __esm({
8006
8232
  "packages/cli/src/env.ts"() {
@@ -8059,18 +8285,200 @@ var init_output = __esm({
8059
8285
  }
8060
8286
  });
8061
8287
 
8288
+ // packages/cli/src/publish-client.ts
8289
+ import { chmodSync, existsSync as existsSync20, mkdirSync as mkdirSync4, readFileSync as readFileSync18, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
8290
+ import os4 from "node:os";
8291
+ import path26 from "node:path";
8292
+ function sessionPath() {
8293
+ return process.env["TENDRIL_SESSION_PATH"] ?? path26.join(os4.homedir(), ".tendril", "session.json");
8294
+ }
8295
+ function readStoredSession(file = sessionPath()) {
8296
+ if (!existsSync20(file)) return void 0;
8297
+ try {
8298
+ const parsed = JSON.parse(readFileSync18(file, "utf8"));
8299
+ if (typeof parsed.origin !== "string" || typeof parsed.token !== "string") return void 0;
8300
+ return { origin: parsed.origin, token: parsed.token };
8301
+ } catch {
8302
+ return void 0;
8303
+ }
8304
+ }
8305
+ function writeStoredSession(session, file = sessionPath()) {
8306
+ mkdirSync4(path26.dirname(file), { recursive: true });
8307
+ writeFileSync7(file, `${JSON.stringify(session, null, 2)}
8308
+ `, { mode: 384 });
8309
+ chmodSync(file, 384);
8310
+ }
8311
+ function clearStoredSession(file = sessionPath()) {
8312
+ if (existsSync20(file)) rmSync3(file);
8313
+ }
8314
+ function tokenFor(origin, file = sessionPath()) {
8315
+ const fromEnv = process.env["TENDRIL_TOKEN"];
8316
+ if (fromEnv !== void 0 && fromEnv !== "") {
8317
+ const pinned = (process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
8318
+ if (pinned !== "" && pinned !== origin) return { ok: false, reason: "origin-mismatch", from: "env", boundTo: pinned };
8319
+ return { ok: true, token: fromEnv, from: "env" };
8320
+ }
8321
+ const stored = readStoredSession(file);
8322
+ if (stored === void 0) return { ok: false, reason: "none" };
8323
+ if (stored.origin !== origin) return { ok: false, reason: "origin-mismatch", from: "file", boundTo: stored.origin };
8324
+ return { ok: true, token: stored.token, from: "file" };
8325
+ }
8326
+ function isSecureOrigin(origin) {
8327
+ let url;
8328
+ try {
8329
+ url = new URL(origin);
8330
+ } catch {
8331
+ return false;
8332
+ }
8333
+ if (url.protocol === "https:") return true;
8334
+ return url.protocol === "http:" && (url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "[::1]");
8335
+ }
8336
+ function isRecord(value) {
8337
+ return value !== null && typeof value === "object" && !Array.isArray(value);
8338
+ }
8339
+ function delay(ms) {
8340
+ return new Promise((resolve) => setTimeout(resolve, ms));
8341
+ }
8342
+ var InsecureOriginError, HttpPublishClient, REQUEST_TIMEOUT_MS, RETRIES, BACKOFF_MS;
8343
+ var init_publish_client = __esm({
8344
+ "packages/cli/src/publish-client.ts"() {
8345
+ "use strict";
8346
+ InsecureOriginError = class extends Error {
8347
+ constructor(origin) {
8348
+ super(`${origin} is not https, so a session token sent there would travel in the clear`);
8349
+ this.origin = origin;
8350
+ this.name = "InsecureOriginError";
8351
+ }
8352
+ origin;
8353
+ };
8354
+ HttpPublishClient = class {
8355
+ origin;
8356
+ token;
8357
+ send;
8358
+ constructor(options) {
8359
+ this.origin = options.origin.replace(/\/+$/, "");
8360
+ if (!isSecureOrigin(this.origin)) throw new InsecureOriginError(this.origin);
8361
+ this.token = options.token;
8362
+ this.send = options.fetch ?? globalThis.fetch;
8363
+ }
8364
+ acceptTerms(input) {
8365
+ return this.json("POST", "/api/consent", input);
8366
+ }
8367
+ begin(input) {
8368
+ return this.json("POST", "/api/publications", input, true);
8369
+ }
8370
+ async upload(input) {
8371
+ const encoded = input.relPath.split("/").map(encodeURIComponent).join("/");
8372
+ return this.request("PUT", `/api/publications/${encodeURIComponent(input.publicationId)}/objects/${encoded}`, {
8373
+ body: input.bytes,
8374
+ contentType: "application/octet-stream",
8375
+ retryable: true
8376
+ });
8377
+ }
8378
+ commit(input) {
8379
+ return this.json("POST", `/api/publications/${encodeURIComponent(input.publicationId)}/commit`, {}, true);
8380
+ }
8381
+ issueShareLink(input) {
8382
+ return this.json("POST", `/api/publications/${encodeURIComponent(input.publicationId)}/shares`, {
8383
+ expiresAt: input.expiresAt,
8384
+ recipientEmail: input.recipientEmail
8385
+ });
8386
+ }
8387
+ listShareLinks(input) {
8388
+ return this.request("GET", `/api/publications/${encodeURIComponent(input.publicationId)}/shares`, {
8389
+ body: "",
8390
+ contentType: "application/json",
8391
+ retryable: true
8392
+ });
8393
+ }
8394
+ revokeShareLink(input) {
8395
+ return this.request("DELETE", `/api/publications/${encodeURIComponent(input.publicationId)}/shares/${encodeURIComponent(input.shareLinkId)}`, {
8396
+ body: "",
8397
+ contentType: "application/json",
8398
+ retryable: true
8399
+ });
8400
+ }
8401
+ json(method, pathname, body, retryable = false) {
8402
+ return this.request(method, pathname, { body: JSON.stringify(body), contentType: "application/json", retryable });
8403
+ }
8404
+ async request(method, pathname, init) {
8405
+ let response;
8406
+ let lastError = "";
8407
+ const attempts = init.retryable === true ? RETRIES : 1;
8408
+ for (let attempt = 1; ; attempt += 1) {
8409
+ try {
8410
+ response = await this.send(`${this.origin}${pathname}`, {
8411
+ method,
8412
+ headers: { authorization: `Bearer ${this.token}`, "content-type": init.contentType },
8413
+ // GET and DELETE carry nothing. `fetch` THROWS on a GET with a
8414
+ // body — even an empty string — so this is a hard requirement
8415
+ // rather than tidiness.
8416
+ ...method === "GET" || method === "HEAD" || init.body === "" ? {} : { body: init.body },
8417
+ // A publish that hangs forever is worse than one that fails:
8418
+ // the user cannot tell it from slow, and there is nothing to
8419
+ // act on. Node's fetch has no default timeout at all.
8420
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
8421
+ });
8422
+ } catch (error) {
8423
+ lastError = error.message;
8424
+ if (attempt >= attempts) {
8425
+ return {
8426
+ ok: false,
8427
+ status: 0,
8428
+ refusal: `could not reach ${this.origin} after ${String(attempt)} ${attempt === 1 ? "try" : "tries"}: ${lastError}`
8429
+ };
8430
+ }
8431
+ await delay(BACKOFF_MS * attempt);
8432
+ continue;
8433
+ }
8434
+ if (response.status >= 500 && attempt < attempts) {
8435
+ await delay(BACKOFF_MS * attempt);
8436
+ continue;
8437
+ }
8438
+ break;
8439
+ }
8440
+ const text = await response.text();
8441
+ let parsed;
8442
+ try {
8443
+ parsed = JSON.parse(text);
8444
+ } catch {
8445
+ return {
8446
+ ok: false,
8447
+ status: response.status,
8448
+ refusal: `${this.origin} answered with ${String(response.status)} and something that is not JSON, so it is probably not a Tendril portal`
8449
+ };
8450
+ }
8451
+ const record = parsed ?? {};
8452
+ if (response.ok) return { ok: true, value: parsed };
8453
+ return {
8454
+ ok: false,
8455
+ status: response.status,
8456
+ refusal: typeof record["refusal"] === "string" ? record["refusal"] : `the portal refused with ${String(response.status)}`,
8457
+ ...Array.isArray(record["detail"]) ? { detail: record["detail"] } : {},
8458
+ ...Array.isArray(record["missing"]) ? { missing: record["missing"] } : {},
8459
+ ...isRecord(record["needsConsent"]) ? { needsConsent: record["needsConsent"] } : {},
8460
+ ...isRecord(record["needsConfirmation"]) ? { needsConfirmation: record["needsConfirmation"] } : {}
8461
+ };
8462
+ }
8463
+ };
8464
+ REQUEST_TIMEOUT_MS = 12e4;
8465
+ RETRIES = 3;
8466
+ BACKOFF_MS = 500;
8467
+ }
8468
+ });
8469
+
8062
8470
  // packages/cli/src/entitlement.ts
8063
- import { chmodSync, existsSync as existsSync20, mkdirSync as mkdirSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "node:fs";
8471
+ import { chmodSync as chmodSync2, existsSync as existsSync21, mkdirSync as mkdirSync5, readFileSync as readFileSync19, writeFileSync as writeFileSync8 } from "node:fs";
8064
8472
  import crypto from "node:crypto";
8065
- import os4 from "node:os";
8066
- import path25 from "node:path";
8473
+ import os5 from "node:os";
8474
+ import path27 from "node:path";
8067
8475
  function entitlementPath() {
8068
- return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path25.join(os4.homedir(), ".tendril", "entitlement.json");
8476
+ return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path27.join(os5.homedir(), ".tendril", "entitlement.json");
8069
8477
  }
8070
8478
  function readStoredEntitlement(file = entitlementPath()) {
8071
- if (!existsSync20(file)) return void 0;
8479
+ if (!existsSync21(file)) return void 0;
8072
8480
  try {
8073
- const parsed = JSON.parse(readFileSync16(file, "utf8"));
8481
+ const parsed = JSON.parse(readFileSync19(file, "utf8"));
8074
8482
  if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
8075
8483
  return { token: parsed.token, lastRefreshAt: parsed.lastRefreshAt };
8076
8484
  } catch {
@@ -8078,10 +8486,10 @@ function readStoredEntitlement(file = entitlementPath()) {
8078
8486
  }
8079
8487
  }
8080
8488
  function writeStoredEntitlement(stored, file = entitlementPath()) {
8081
- mkdirSync4(path25.dirname(file), { recursive: true });
8082
- writeFileSync7(file, `${JSON.stringify(stored, null, 2)}
8489
+ mkdirSync5(path27.dirname(file), { recursive: true });
8490
+ writeFileSync8(file, `${JSON.stringify(stored, null, 2)}
8083
8491
  `);
8084
- chmodSync(file, 384);
8492
+ chmodSync2(file, 384);
8085
8493
  }
8086
8494
  function parseEntitlementToken(token) {
8087
8495
  if (!token.startsWith(ENT_PREFIX)) return { error: "not a tendril entitlement token" };
@@ -8163,9 +8571,9 @@ var init_entitlement = __esm({
8163
8571
 
8164
8572
  // packages/cli/src/commands/doctor.ts
8165
8573
  import { spawnSync } from "node:child_process";
8166
- import { existsSync as existsSync21, readFileSync as readFileSync17, readdirSync as readdirSync7 } from "node:fs";
8167
- import os5 from "node:os";
8168
- import path26 from "node:path";
8574
+ import { existsSync as existsSync22, readFileSync as readFileSync20, readdirSync as readdirSync8 } from "node:fs";
8575
+ import os6 from "node:os";
8576
+ import path28 from "node:path";
8169
8577
  function withDeadline(work, ms) {
8170
8578
  return Promise.race([
8171
8579
  work,
@@ -8225,19 +8633,19 @@ async function runDoctorChecks(options) {
8225
8633
  remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
8226
8634
  });
8227
8635
  }
8228
- const fontManifest = path26.join(fontCacheDir(), "manifest.json");
8636
+ const fontManifest = path28.join(fontCacheDir(), "manifest.json");
8229
8637
  checks.push(
8230
- existsSync21(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync17(fontManifest, "utf8")).length} faces)` } : {
8638
+ existsSync22(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync20(fontManifest, "utf8")).length} faces)` } : {
8231
8639
  name: "font-cache",
8232
8640
  ok: true,
8233
8641
  detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
8234
8642
  remediation: `Nothing to do now: \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` fetches exactly what a recording declares, and generate/verify name that command \u2014 with the set filled in \u2014 when they need it.`
8235
8643
  }
8236
8644
  );
8237
- const pluginRoot = path26.join(os5.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
8238
- if (existsSync21(pluginRoot)) {
8645
+ const pluginRoot = path28.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
8646
+ if (existsSync22(pluginRoot)) {
8239
8647
  try {
8240
- const versions = readdirSync7(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
8648
+ const versions = readdirSync8(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
8241
8649
  const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
8242
8650
  if (newest !== void 0) {
8243
8651
  const skewed = versionIsNewer(newest, cliVersion());
@@ -8271,13 +8679,52 @@ async function runDoctorChecks(options) {
8271
8679
  checks.push(
8272
8680
  ent.ok ? ent.mode === "pre-launch" ? { name: "entitlement", ok: true, detail: "pre-launch build \u2014 entitlement gates present but unarmed (no public key shipped); record/generate run freely" } : { name: "entitlement", ok: true, detail: `active plan "${ent.claims.plan}" until ${new Date(ent.claims.exp).toISOString().slice(0, 10)}${ent.stale ? " (stale \u2014 renews at next opportunity)" : ""}` } : { name: "entitlement", ok: false, detail: ent.error, remediation: ent.remediation }
8273
8681
  );
8682
+ checks.push(await portalSessionCheck(options.fetchImpl ?? fetch));
8274
8683
  const figmaToken = resolveCredential("FIGMA_TOKEN");
8275
8684
  checks.push({
8276
8685
  name: "figma-pat",
8277
8686
  ok: true,
8278
8687
  detail: figmaToken ? "FIGMA_TOKEN configured but unused \u2014 recording runs over the Figma MCP transport" : "FIGMA_TOKEN not set \u2014 not needed; recording runs over the Figma MCP transport"
8279
8688
  });
8280
- return { ok: checks.filter((c) => c.name !== "figma-pat" && c.name !== "openrouter-key" && c.name !== "path-skew").every((c) => c.ok), checks };
8689
+ return {
8690
+ ok: checks.filter((c) => c.name !== "figma-pat" && c.name !== "openrouter-key" && c.name !== "path-skew" && c.name !== "portal-session").every((c) => c.ok),
8691
+ checks
8692
+ };
8693
+ }
8694
+ async function portalSessionCheck(fetchImpl) {
8695
+ const stored = readStoredSession();
8696
+ const connectHint = `Run ${tendrilCommand("login")} and approve in your browser (agents: call the tendril_login tool, relay the link and code, then tendril_login_wait) \u2014 or mint a token under "Connect your CLI" in your portal library and use ${tendrilCommand("login --paste")}.`;
8697
+ if (stored === void 0) {
8698
+ return { name: "portal-session", ok: true, detail: `not signed in to a portal \u2014 publishing needs it, verification never does. ${connectHint}` };
8699
+ }
8700
+ let response;
8701
+ try {
8702
+ const ctl = new AbortController();
8703
+ const timer = setTimeout(() => ctl.abort(), 5e3);
8704
+ timer.unref?.();
8705
+ response = await fetchImpl(`${stored.origin}/api/whoami`, { headers: { authorization: `Bearer ${stored.token}` }, signal: ctl.signal });
8706
+ clearTimeout(timer);
8707
+ } catch {
8708
+ return { name: "portal-session", ok: true, detail: `${stored.origin} did not answer \u2014 the stored session could not be checked. Publishing will say so precisely if it is actually dead.` };
8709
+ }
8710
+ if (!response.ok) {
8711
+ return {
8712
+ name: "portal-session",
8713
+ ok: false,
8714
+ detail: `the session stored for ${stored.origin} no longer works there (${String(response.status)}) \u2014 it was revoked or expired`,
8715
+ remediation: connectHint
8716
+ };
8717
+ }
8718
+ const body = await response.json();
8719
+ const email = typeof body.email === "string" && body.email !== "" ? ` as ${body.email}` : "";
8720
+ const expiry = typeof body.sessionExpiresAt === "string" ? body.sessionExpiresAt : null;
8721
+ const daysLeft = expiry === null ? null : Math.floor((Date.parse(expiry) - Date.now()) / 864e5);
8722
+ return {
8723
+ name: "portal-session",
8724
+ ok: true,
8725
+ detail: `signed in to ${stored.origin}${email}${expiry === null ? "" : `, session good until ${expiry.slice(0, 10)}`}`,
8726
+ ...daysLeft !== null && daysLeft <= 7 ? { remediation: `That is ${String(daysLeft)} day(s) away \u2014 run ${tendrilCommand("login")} soon for a fresh session.` } : {}
8727
+ };
8281
8728
  }
8282
8729
  function probeVersion(binary) {
8283
8730
  const windowsShim = /\.(cmd|bat)$/i.test(binary);
@@ -8352,6 +8799,7 @@ var init_doctor = __esm({
8352
8799
  init_environment();
8353
8800
  init_invocation();
8354
8801
  init_output();
8802
+ init_publish_client();
8355
8803
  init_entitlement();
8356
8804
  DEFAULT_MCP_URL = "http://127.0.0.1:3845/mcp";
8357
8805
  DOCTOR_DESCRIPTION = {
@@ -9467,8 +9915,8 @@ var init_engine_curated = __esm({
9467
9915
  });
9468
9916
 
9469
9917
  // packages/generate/src/loop.ts
9470
- import { existsSync as existsSync23, mkdirSync as mkdirSync6, readFileSync as readFileSync19, renameSync, writeFileSync as writeFileSync9 } from "node:fs";
9471
- import path29 from "node:path";
9918
+ import { existsSync as existsSync24, mkdirSync as mkdirSync7, readFileSync as readFileSync22, renameSync, writeFileSync as writeFileSync10 } from "node:fs";
9919
+ import path31 from "node:path";
9472
9920
  import { z as z13 } from "zod";
9473
9921
  function objective(scores, behaviors) {
9474
9922
  const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
@@ -9510,9 +9958,9 @@ ${absentLines.join("\n")}` : ""}
9510
9958
  Fix the FAIL configs (region coordinates are in the recorded screenshot's frame; ink<1 means recorded foreground pixels your render does not cover). Reading the pair: ink credits a recorded pixel when ANY render ink lands within 1px of it, so ink=1 proves coverage \u2014 not presence, shape, or colour. A wrong-shaped mark over the right region, or a wrong-coloured one that is still ink, scores 1.000; and a recorded mark within ~30 channel-sum of the backdrop (#f6f6f6 on white is 27) is not ink at all, so it can be absent at ink 1.000 with no MISSING line. With ink=1 AND low sim, coverage held while pixels differ, so start with the TWO causes below \u2014 but never read ink=1 as "nothing is missing": confirm every faint or small recorded mark (hairlines, dividers, low-contrast controls) exists in your render. GEOMETRY: wrong position/size/radius; the diff shows shifted edges and bands. WRONG TEXT WEIGHT OR FACE: the diff shows a uniform haze over glyph runs; CSS binds the font FAMILY before the weight, a later family in the stack is never consulted for a weight the first one lacks, and font-synthesis: none rules out faux-bold \u2014 so a stack led by a family the kit holds at one weight renders every other weight in that face, silently. Check the font stack against the kit's cached faces before concluding geometry is at fault. LOW ink with high sim means recorded marks are absent or painted invisibly. Do not regress PASS configs. ${mode === "files" ? "Update the files in your candidate directory and re-run the score." : "Reply with the complete corrected files in the same FILE format."}`;
9511
9959
  }
9512
9960
  function archivePriorRun(outDir) {
9513
- if (!existsSync23(path29.join(outDir, "run-log.json")) && !existsSync23(path29.join(outDir, "loop-state.json"))) return void 0;
9961
+ if (!existsSync24(path31.join(outDir, "run-log.json")) && !existsSync24(path31.join(outDir, "loop-state.json"))) return void 0;
9514
9962
  let n = 1;
9515
- while (existsSync23(`${outDir}-prev-${n}`)) n += 1;
9963
+ while (existsSync24(`${outDir}-prev-${n}`)) n += 1;
9516
9964
  renameSync(outDir, `${outDir}-prev-${n}`);
9517
9965
  return `${outDir}-prev-${n}`;
9518
9966
  }
@@ -9521,14 +9969,14 @@ async function runEngineLoop(opts) {
9521
9969
  const plateau = opts.plateau ?? 2;
9522
9970
  const progress = opts.onProgress ?? (() => {
9523
9971
  });
9524
- const statePath = path29.join(opts.outDir, "loop-state.json");
9525
- const resuming = opts.resume === true && existsSync23(statePath);
9972
+ const statePath = path31.join(opts.outDir, "loop-state.json");
9973
+ const resuming = opts.resume === true && existsSync24(statePath);
9526
9974
  if (!resuming) {
9527
9975
  const archived = archivePriorRun(opts.outDir);
9528
9976
  if (archived !== void 0) progress(`previous run archived to ${archived}`);
9529
9977
  }
9530
- mkdirSync6(opts.outDir, { recursive: true });
9531
- const scratch = path29.join(opts.outDir, ".candidate");
9978
+ mkdirSync7(opts.outDir, { recursive: true });
9979
+ const scratch = path31.join(opts.outDir, ".candidate");
9532
9980
  let attempts = [];
9533
9981
  let log = [];
9534
9982
  let best;
@@ -9536,7 +9984,7 @@ async function runEngineLoop(opts) {
9536
9984
  let nonAccepted = 0;
9537
9985
  let stopReason = "max-iterations";
9538
9986
  if (resuming) {
9539
- const restored = LoopStateSchema.parse(JSON.parse(readFileSync19(statePath, "utf8")));
9987
+ const restored = LoopStateSchema.parse(JSON.parse(readFileSync22(statePath, "utf8")));
9540
9988
  attempts = restored.attempts;
9541
9989
  log = restored.iterations;
9542
9990
  spentUsd = restored.spentUsd;
@@ -9551,12 +9999,12 @@ async function runEngineLoop(opts) {
9551
9999
  progress(`resumed: ${log.length} iteration(s), $${spentUsd.toFixed(3)} spent, best pass=${best?.objective[0] ?? 0}`);
9552
10000
  }
9553
10001
  const persist = () => {
9554
- writeFileSync9(statePath, `${JSON.stringify({ version: 1, spentUsd, attempts, iterations: log }, null, 1)}
10002
+ writeFileSync10(statePath, `${JSON.stringify({ version: 1, spentUsd, attempts, iterations: log }, null, 1)}
9555
10003
  `);
9556
10004
  };
9557
10005
  const writeCandidate = (files) => {
9558
- mkdirSync6(scratch, { recursive: true });
9559
- for (const [name, content] of Object.entries(files)) writeFileSync9(path29.join(scratch, name), content);
10006
+ mkdirSync7(scratch, { recursive: true });
10007
+ for (const [name, content] of Object.entries(files)) writeFileSync10(path31.join(scratch, name), content);
9560
10008
  };
9561
10009
  const scoreCandidate = async (candidate, iter, usd, modelMs) => {
9562
10010
  writeCandidate(candidate.files);
@@ -9614,8 +10062,8 @@ async function runEngineLoop(opts) {
9614
10062
  const usd = candidate.usage?.usd ?? 0;
9615
10063
  spentUsd += usd;
9616
10064
  if (candidate.raw !== void 0) {
9617
- mkdirSync6(path29.join(opts.outDir, "responses"), { recursive: true });
9618
- writeFileSync9(path29.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
10065
+ mkdirSync7(path31.join(opts.outDir, "responses"), { recursive: true });
10066
+ writeFileSync10(path31.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
9619
10067
  }
9620
10068
  if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
9621
10069
  const finish = candidate.usage?.finishReason ?? "?";
@@ -9641,10 +10089,10 @@ async function runEngineLoop(opts) {
9641
10089
  }
9642
10090
  }
9643
10091
  }
9644
- if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync9(path29.join(opts.outDir, name), content);
10092
+ if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(path31.join(opts.outDir, name), content);
9645
10093
  const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
9646
- writeFileSync9(
9647
- path29.join(opts.outDir, "run-log.json"),
10094
+ writeFileSync10(
10095
+ path31.join(opts.outDir, "run-log.json"),
9648
10096
  `${JSON.stringify(
9649
10097
  {
9650
10098
  ...opts.meta,
@@ -9711,8 +10159,8 @@ var init_loop2 = __esm({
9711
10159
  });
9712
10160
 
9713
10161
  // packages/generate/src/brief.ts
9714
- import { existsSync as existsSync24, readFileSync as readFileSync20 } from "node:fs";
9715
- import path30 from "node:path";
10162
+ import { existsSync as existsSync25, readFileSync as readFileSync23 } from "node:fs";
10163
+ import path32 from "node:path";
9716
10164
  import { PNG as PNG3 } from "pngjs";
9717
10165
  function singleAxes2(name) {
9718
10166
  const parsed = parseVariantAxes(name);
@@ -10152,15 +10600,15 @@ function authorBehaviors(api, extras = {}) {
10152
10600
  };
10153
10601
  }
10154
10602
  function envelopeText(file) {
10155
- return envelopeFirstTextPart(JSON.parse(readFileSync20(file, "utf8")));
10603
+ return envelopeFirstTextPart(JSON.parse(readFileSync23(file, "utf8")));
10156
10604
  }
10157
10605
  function metadataText(file) {
10158
- return envelopeTextContent(JSON.parse(readFileSync20(file, "utf8")));
10606
+ return envelopeTextContent(JSON.parse(readFileSync23(file, "utf8")));
10159
10607
  }
10160
10608
  function dismissEvidence(setDir, repSlugs) {
10161
10609
  for (const slug of repSlugs) {
10162
- const f = path30.join(setDir, slug, "get_design_context.json");
10163
- if (!existsSync24(f)) continue;
10610
+ const f = path32.join(setDir, slug, "get_design_context.json");
10611
+ if (!existsSync25(f)) continue;
10164
10612
  const text = envelopeText(f);
10165
10613
  const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*true\b/i.exec(text);
10166
10614
  if (propHit !== null) return { evidence: `emission prop "${propHit[1]}"`, visibleReps: [] };
@@ -10187,9 +10635,9 @@ function dismissEvidence(setDir, repSlugs) {
10187
10635
  const glyphIsTheComponent = (() => {
10188
10636
  const slugToCheck = vis.visibleIn[0];
10189
10637
  if (slugToCheck === void 0) return false;
10190
- const metaFile = path30.join(setDir, slugToCheck, "get_metadata.json");
10638
+ const metaFile = path32.join(setDir, slugToCheck, "get_metadata.json");
10191
10639
  try {
10192
- const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync20(metaFile, "utf8"))));
10640
+ const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync23(metaFile, "utf8"))));
10193
10641
  if (root.children.length !== 1) return false;
10194
10642
  const contains = (n) => normalizedLayerName(n.name) === norm2 || n.children.some(contains);
10195
10643
  return contains(root.children[0]);
@@ -10209,10 +10657,10 @@ function dismissEvidence(setDir, repSlugs) {
10209
10657
  return void 0;
10210
10658
  }
10211
10659
  function recordedReferencePng(setDir, slug) {
10212
- const f = path30.join(setDir, slug, "get_screenshot.json");
10213
- if (!existsSync24(f)) return void 0;
10660
+ const f = path32.join(setDir, slug, "get_screenshot.json");
10661
+ if (!existsSync25(f)) return void 0;
10214
10662
  try {
10215
- const env = JSON.parse(readFileSync20(f, "utf8")).content.find((c) => c.type === "image");
10663
+ const env = JSON.parse(readFileSync23(f, "utf8")).content.find((c) => c.type === "image");
10216
10664
  return env?.data === void 0 ? void 0 : Uint8Array.from(Buffer.from(env.data, "base64"));
10217
10665
  } catch {
10218
10666
  return void 0;
@@ -10253,7 +10701,7 @@ function recordedFontNeeds(setDir, opts = {}) {
10253
10701
  for (const m of text.matchAll(/font-\['([^':\]]+)(?::([^'\]]+))?'/g)) famAdd(m[1], m[2] === void 0 ? void 0 : styleWeight(m[2]));
10254
10702
  for (const m of text.matchAll(/family-name:var\([^,)]*,\s*'([^':\]]+)(?::([^'\]]+))?'/g)) famAdd(m[1], m[2] === void 0 ? void 0 : styleWeight(m[2]));
10255
10703
  for (const m of text.matchAll(/\bfont-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)\b/g)) unpaired.add(STYLE_WEIGHTS2[m[1]]);
10256
- for (const m of text.matchAll(/font-weight:\s*([1-9]00)\b/g)) unpaired.add(Number(m[1]));
10704
+ for (const m of text.matchAll(/font-weight:\s*([1-9]\d{2})\b/g)) unpaired.add(Number(m[1]));
10257
10705
  };
10258
10706
  const fromDefs = (text) => {
10259
10707
  let defs;
@@ -10271,15 +10719,34 @@ function recordedFontNeeds(setDir, opts = {}) {
10271
10719
  }
10272
10720
  if (/^[A-Za-z][A-Za-z0-9 ]{1,39}$/.test(v.trim())) famAdd(v.trim());
10273
10721
  }
10722
+ fromFontCalls(defs);
10274
10723
  };
10724
+ function fromFontCalls(defs) {
10725
+ for (const v of Object.values(defs)) {
10726
+ if (typeof v !== "string") continue;
10727
+ const call = /\bFont\(\s*family:\s*"?([^",]+)"?\s*,([^)]*)\)/.exec(v);
10728
+ if (call === null) continue;
10729
+ const rest = call[2];
10730
+ const explicit = /\bweight:\s*([1-9]\d{2})\b/.exec(rest);
10731
+ const styleRef = /\bstyle:\s*([^,)]+)/.exec(rest);
10732
+ let weight = explicit === null ? void 0 : Number(explicit[1]);
10733
+ if (weight === void 0 && styleRef !== null) {
10734
+ const ref = styleRef[1].trim().replace(/^"|"$/g, "");
10735
+ weight = styleWeight((typeof defs[ref] === "string" ? defs[ref] : ref).trim());
10736
+ }
10737
+ const famRef = call[1].trim();
10738
+ const family = typeof defs[famRef] === "string" ? defs[famRef].trim() : famRef;
10739
+ if (/^[A-Za-z][A-Za-z0-9 ]{1,39}$/.test(family)) famAdd(family, weight);
10740
+ }
10741
+ }
10275
10742
  const manifest = loadManifest(setDir);
10276
- const setDefs = path30.join(setDir, "get_variable_defs.json");
10277
- if (existsSync24(setDefs)) fromDefs(envelopeText(setDefs));
10743
+ const setDefs = path32.join(setDir, "get_variable_defs.json");
10744
+ if (existsSync25(setDefs)) fromDefs(envelopeText(setDefs));
10278
10745
  for (const rep of manifest.reps) {
10279
- const ctx = path30.join(setDir, rep.slug, "get_design_context.json");
10280
- if (existsSync24(ctx)) fromEmission(envelopeText(ctx));
10281
- const defs = path30.join(setDir, rep.slug, "get_variable_defs.json");
10282
- if (existsSync24(defs)) fromDefs(envelopeText(defs));
10746
+ const ctx = path32.join(setDir, rep.slug, "get_design_context.json");
10747
+ if (existsSync25(ctx)) fromEmission(envelopeText(ctx));
10748
+ const defs = path32.join(setDir, rep.slug, "get_variable_defs.json");
10749
+ if (existsSync25(defs)) fromDefs(envelopeText(defs));
10283
10750
  }
10284
10751
  return [...byFamily.entries()].map(([family, paired]) => {
10285
10752
  const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
@@ -10290,10 +10757,10 @@ function symbolFontGlyphCount(setDir, reps) {
10290
10757
  const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
10291
10758
  const glyphs = /* @__PURE__ */ new Set();
10292
10759
  for (const rep of reps) {
10293
- const file = path30.join(setDir, rep, "get_metadata.json");
10294
- if (!existsSync24(file)) continue;
10760
+ const file = path32.join(setDir, rep, "get_metadata.json");
10761
+ if (!existsSync25(file)) continue;
10295
10762
  try {
10296
- const text = JSON.parse(readFileSync20(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
10763
+ const text = JSON.parse(readFileSync23(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
10297
10764
  for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
10298
10765
  const name = decodeXmlEntities(m[1]).replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)));
10299
10766
  if (PUA.test(name)) glyphs.add(name);
@@ -10319,8 +10786,8 @@ function recordedTextSlots(setDir, repSlugs) {
10319
10786
  const propRep = [];
10320
10787
  const perRep = [];
10321
10788
  for (const slug of repSlugs) {
10322
- const f = path30.join(setDir, slug, "get_design_context.json");
10323
- if (!existsSync24(f)) continue;
10789
+ const f = path32.join(setDir, slug, "get_design_context.json");
10790
+ if (!existsSync25(f)) continue;
10324
10791
  const code = envelopeText(f);
10325
10792
  const props = /* @__PURE__ */ new Map();
10326
10793
  for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
@@ -10346,8 +10813,8 @@ function recordedTextSlots(setDir, repSlugs) {
10346
10813
  const axisValuesBySlug = /* @__PURE__ */ new Map();
10347
10814
  const valuesByAxis = /* @__PURE__ */ new Map();
10348
10815
  for (const slug of repSlugs) {
10349
- const metaFile = path30.join(setDir, slug, "get_metadata.json");
10350
- if (!existsSync24(metaFile)) continue;
10816
+ const metaFile = path32.join(setDir, slug, "get_metadata.json");
10817
+ if (!existsSync25(metaFile)) continue;
10351
10818
  const name = symbolName(metadataText(metaFile));
10352
10819
  if (name === void 0) continue;
10353
10820
  const values = /* @__PURE__ */ new Set();
@@ -10462,8 +10929,8 @@ function authorTaskFromSet(setDir, opts = {}) {
10462
10929
  const poses = [];
10463
10930
  const missing = [];
10464
10931
  for (const rep of manifest.reps) {
10465
- const metaFile = path30.join(setDir, rep.slug, "get_metadata.json");
10466
- if (!existsSync24(metaFile)) {
10932
+ const metaFile = path32.join(setDir, rep.slug, "get_metadata.json");
10933
+ if (!existsSync25(metaFile)) {
10467
10934
  missing.push(rep.slug);
10468
10935
  continue;
10469
10936
  }
@@ -10477,8 +10944,8 @@ function authorTaskFromSet(setDir, opts = {}) {
10477
10944
  if (missing.length > 0) {
10478
10945
  throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
10479
10946
  }
10480
- const setMeta = path30.join(setDir, "get_metadata.json");
10481
- const latticeNames = manifest.latticeNames ?? (existsSync24(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
10947
+ const setMeta = path32.join(setDir, "get_metadata.json");
10948
+ const latticeNames = manifest.latticeNames ?? (existsSync25(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
10482
10949
  const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
10483
10950
  const recordedFonts = recordedFontFamilies(setDir);
10484
10951
  const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
@@ -10679,17 +11146,17 @@ var init_brief = __esm({
10679
11146
  });
10680
11147
 
10681
11148
  // packages/generate/src/segments.ts
10682
- import { existsSync as existsSync25, readFileSync as readFileSync21, readdirSync as readdirSync9 } from "node:fs";
10683
- import path31 from "node:path";
11149
+ import { existsSync as existsSync26, readFileSync as readFileSync24, readdirSync as readdirSync10 } from "node:fs";
11150
+ import path33 from "node:path";
10684
11151
  function repText(set, rep, tool) {
10685
- const env = JSON.parse(readFileSync21(path31.join(set, rep, `${tool}.json`), "utf8"));
11152
+ const env = JSON.parse(readFileSync24(path33.join(set, rep, `${tool}.json`), "utf8"));
10686
11153
  return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
10687
11154
  }
10688
11155
  function refPngDims(set, rep) {
10689
- const f = path31.join(set, rep, "get_screenshot.json");
10690
- if (!existsSync25(f)) return void 0;
11156
+ const f = path33.join(set, rep, "get_screenshot.json");
11157
+ if (!existsSync26(f)) return void 0;
10691
11158
  try {
10692
- const env = JSON.parse(readFileSync21(f, "utf8")).content.find((c) => c.type === "image");
11159
+ const env = JSON.parse(readFileSync24(f, "utf8")).content.find((c) => c.type === "image");
10693
11160
  if (env?.data === void 0) return void 0;
10694
11161
  const buf = Buffer.from(env.data, "base64");
10695
11162
  if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return void 0;
@@ -10755,20 +11222,20 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
10755
11222
  }
10756
11223
  function buildSegments(task, mode = "fenced") {
10757
11224
  const SET = task.set;
10758
- let defsRecorded = existsSync25(path31.join(SET, "get_variable_defs.json"));
11225
+ let defsRecorded = existsSync26(path33.join(SET, "get_variable_defs.json"));
10759
11226
  let rawDefs = {};
10760
- if (existsSync25(path31.join(SET, "get_variable_defs.json"))) {
10761
- const text = envelopeFirstTextPart(JSON.parse(readFileSync21(path31.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
11227
+ if (existsSync26(path33.join(SET, "get_variable_defs.json"))) {
11228
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync24(path33.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
10762
11229
  try {
10763
11230
  rawDefs = JSON.parse(text);
10764
11231
  } catch {
10765
11232
  }
10766
11233
  } else {
10767
11234
  for (const cfg of task.configs) {
10768
- const f = path31.join(SET, cfg.rep, "get_variable_defs.json");
10769
- if (!existsSync25(f)) continue;
11235
+ const f = path33.join(SET, cfg.rep, "get_variable_defs.json");
11236
+ if (!existsSync26(f)) continue;
10770
11237
  defsRecorded = true;
10771
- const text = envelopeFirstTextPart(JSON.parse(readFileSync21(f, "utf8"))) || "{}";
11238
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync24(f, "utf8"))) || "{}";
10772
11239
  try {
10773
11240
  for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
10774
11241
  } catch {
@@ -10776,8 +11243,8 @@ function buildSegments(task, mode = "fenced") {
10776
11243
  }
10777
11244
  }
10778
11245
  const emissionTexts = task.configs.map((cfg) => {
10779
- const f = path31.join(SET, cfg.rep, "get_design_context.json");
10780
- return existsSync25(f) ? envelopeFirstTextPart(JSON.parse(readFileSync21(f, "utf8"))) : "";
11246
+ const f = path33.join(SET, cfg.rep, "get_design_context.json");
11247
+ return existsSync26(f) ? envelopeFirstTextPart(JSON.parse(readFileSync24(f, "utf8"))) : "";
10781
11248
  });
10782
11249
  const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
10783
11250
  const defs = JSON.stringify(map, null, 1);
@@ -10795,9 +11262,9 @@ TOKEN MAP NEVER RECORDED: this set holds no get_variable_defs envelope, so wheth
10795
11262
  for (const cfg of task.configs) {
10796
11263
  const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
10797
11264
  const emission = stripFigmaInstructions(repText(SET, cfg.rep, "get_design_context"));
10798
- const assets = readdirSync9(path31.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
11265
+ const assets = readdirSync10(path33.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
10799
11266
  \`\`\`svg
10800
- ${readFileSync21(path31.join(SET, cfg.rep, f), "utf8")}
11267
+ ${readFileSync24(path33.join(SET, cfg.rep, f), "utf8")}
10801
11268
  \`\`\``).join("\n");
10802
11269
  const refNote = (() => {
10803
11270
  const dims = refPngDims(SET, cfg.rep);
@@ -10833,7 +11300,7 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
10833
11300
  } else {
10834
11301
  parts.push(`
10835
11302
  ## Output format
10836
- Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path31.basename(task.set)}-candidate/\` unless you were handed another path. Pass that same directory to \`tendril engine score\` every round and keep writing into it \u2014 the scorer stamps the bundle there, writes its evidence beside your files, and appends its score-history.jsonl lines there (one when a round starts, one when it scores); a fresh directory each round throws all of that away. Do not paste file contents into chat \u2014 the scorer reads the directory.`);
11303
+ Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path33.basename(task.set)}-candidate/\` unless you were handed another path. Pass that same directory to \`tendril engine score\` every round and keep writing into it \u2014 the scorer stamps the bundle there, writes its evidence beside your files, and appends its score-history.jsonl lines there (one when a round starts, one when it scores); a fresh directory each round throws all of that away. Do not paste file contents into chat \u2014 the scorer reads the directory.`);
10837
11304
  }
10838
11305
  return parts.join("\n");
10839
11306
  }
@@ -10900,9 +11367,9 @@ var init_adapter = __esm({
10900
11367
  });
10901
11368
 
10902
11369
  // packages/generate/src/bundle-emit.ts
10903
- import { createHash as createHash5 } from "node:crypto";
10904
- import { copyFileSync, existsSync as existsSync26, mkdirSync as mkdirSync7, readFileSync as readFileSync22, readdirSync as readdirSync10, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "node:fs";
10905
- import path32 from "node:path";
11370
+ import { createHash as createHash6 } from "node:crypto";
11371
+ import { copyFileSync, existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as readFileSync25, readdirSync as readdirSync11, rmSync as rmSync4, writeFileSync as writeFileSync11 } from "node:fs";
11372
+ import path34 from "node:path";
10906
11373
  function pinFromConfigs(configs) {
10907
11374
  const domains = /* @__PURE__ */ new Map();
10908
11375
  const kinds = /* @__PURE__ */ new Map();
@@ -10971,9 +11438,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
10971
11438
  const notices = [];
10972
11439
  const licenseTexts = /* @__PURE__ */ new Map();
10973
11440
  for (const face of faces) {
10974
- const src = path32.join(cacheDir, path32.basename(face.file));
10975
- const target = `./fonts/${path32.basename(face.file)}`;
10976
- const format = FONT_FORMATS[path32.extname(face.file).toLowerCase()] ?? "truetype";
11441
+ const src = path34.join(cacheDir, path34.basename(face.file));
11442
+ const target = `./fonts/${path34.basename(face.file)}`;
11443
+ const format = FONT_FORMATS[path34.extname(face.file).toLowerCase()] ?? "truetype";
10977
11444
  const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
10978
11445
  const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
10979
11446
  const license = normalizeFontLicense(face.license);
@@ -11011,14 +11478,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11011
11478
  `/* ${decl} */`
11012
11479
  );
11013
11480
  }
11014
- } else if (existsSync26(src) && createHash5("sha256").update(readFileSync22(src)).digest("hex") === face.sha256) {
11015
- mkdirSync7(path32.join(bundleDir, "fonts"), { recursive: true });
11016
- copyFileSync(src, path32.join(bundleDir, "fonts", path32.basename(face.file)));
11481
+ } else if (existsSync27(src) && createHash6("sha256").update(readFileSync25(src)).digest("hex") === face.sha256) {
11482
+ mkdirSync8(path34.join(bundleDir, "fonts"), { recursive: true });
11483
+ copyFileSync(src, path34.join(bundleDir, "fonts", path34.basename(face.file)));
11017
11484
  licenseTexts.set(terms.file, terms.text);
11018
11485
  const upstream = upstreamAttribution(face);
11019
11486
  notices.push(
11020
11487
  "",
11021
- `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path32.basename(face.file)}`,
11488
+ `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path34.basename(face.file)}`,
11022
11489
  ` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
11023
11490
  ` source: ${face.source}`,
11024
11491
  ` sha256: ${face.sha256}`,
@@ -11032,9 +11499,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11032
11499
  }
11033
11500
  if (lines.length === 0) return null;
11034
11501
  if (notices.length > 0) {
11035
- const fontsDir = path32.join(bundleDir, "fonts");
11036
- for (const [file, text] of licenseTexts) writeFileSync10(path32.join(fontsDir, file), text);
11037
- writeFileSync10(path32.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
11502
+ const fontsDir = path34.join(bundleDir, "fonts");
11503
+ for (const [file, text] of licenseTexts) writeFileSync11(path34.join(fontsDir, file), text);
11504
+ writeFileSync11(path34.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
11038
11505
  `);
11039
11506
  header.push(
11040
11507
  "/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
@@ -11046,10 +11513,10 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11046
11513
  `;
11047
11514
  }
11048
11515
  function countLatticeSymbols(setDir) {
11049
- const manifestFile = path32.join(setDir, "recording-set.json");
11050
- if (existsSync26(manifestFile)) {
11516
+ const manifestFile = path34.join(setDir, "recording-set.json");
11517
+ if (existsSync27(manifestFile)) {
11051
11518
  try {
11052
- const stored = JSON.parse(readFileSync22(manifestFile, "utf8"));
11519
+ const stored = JSON.parse(readFileSync25(manifestFile, "utf8"));
11053
11520
  if (stored.variantScope !== "component-set") return null;
11054
11521
  const lattice = stored.latticeNames;
11055
11522
  if (lattice !== void 0 && lattice.length > 0) return lattice.length;
@@ -11057,13 +11524,13 @@ function countLatticeSymbols(setDir) {
11057
11524
  }
11058
11525
  }
11059
11526
  const files = [
11060
- path32.join(setDir, "get_metadata.json"),
11061
- ...existsSync26(setDir) ? readdirSync10(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path32.join(setDir, f)) : []
11062
- ].filter((f) => existsSync26(f));
11527
+ path34.join(setDir, "get_metadata.json"),
11528
+ ...existsSync27(setDir) ? readdirSync11(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path34.join(setDir, f)) : []
11529
+ ].filter((f) => existsSync27(f));
11063
11530
  if (files.length === 0) return null;
11064
11531
  let count = 0;
11065
11532
  for (const f of files) {
11066
- const text = envelopeTextContent(JSON.parse(readFileSync22(f, "utf8")));
11533
+ const text = envelopeTextContent(JSON.parse(readFileSync25(f, "utf8")));
11067
11534
  count += [...text.matchAll(/name="([^"]*)"/g)].filter((m) => m[1].includes("=")).length;
11068
11535
  }
11069
11536
  return count > 0 ? count : null;
@@ -11071,23 +11538,23 @@ function countLatticeSymbols(setDir) {
11071
11538
  function recordingSetHash(setDir, configs) {
11072
11539
  const relPaths = [];
11073
11540
  for (const name of ["recording-set.json", "completeness-manifest.json", "get_variable_defs.json", "get_metadata.json"]) {
11074
- if (existsSync26(path32.join(setDir, name))) relPaths.push(name);
11541
+ if (existsSync27(path34.join(setDir, name))) relPaths.push(name);
11075
11542
  }
11076
11543
  for (const cfg of configs) {
11077
11544
  for (const f of ["get_design_context.json", "get_metadata.json", "get_screenshot.json", "get_variable_defs.json"]) {
11078
- if (existsSync26(path32.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
11545
+ if (existsSync27(path34.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
11079
11546
  }
11080
- if (existsSync26(path32.join(setDir, cfg.rep))) {
11081
- for (const asset of readdirSync10(path32.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
11547
+ if (existsSync27(path34.join(setDir, cfg.rep))) {
11548
+ for (const asset of readdirSync11(path34.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
11082
11549
  relPaths.push(`${cfg.rep}/${asset}`);
11083
11550
  }
11084
11551
  }
11085
11552
  }
11086
11553
  return hashRecordingSet(
11087
11554
  relPaths,
11088
- (p) => new Uint8Array(readFileSync22(path32.join(setDir, p))),
11555
+ (p) => new Uint8Array(readFileSync25(path34.join(setDir, p))),
11089
11556
  (chunks) => {
11090
- const h = createHash5("sha256");
11557
+ const h = createHash6("sha256");
11091
11558
  for (const c of chunks) h.update(c);
11092
11559
  return h.digest("hex");
11093
11560
  }
@@ -11097,6 +11564,17 @@ function statusOf(s) {
11097
11564
  const tier = tierOf(s, BARS.cert);
11098
11565
  return tier === "certified" && (s.absentInk?.length ?? 0) > 0 ? "pass" : tier;
11099
11566
  }
11567
+ function kitIdentity(setDir) {
11568
+ try {
11569
+ const m = loadManifest(setDir);
11570
+ return {
11571
+ ...typeof m.figmaFile === "string" ? { figmaFile: m.figmaFile } : {},
11572
+ ...typeof m.componentSetNode === "string" ? { componentSetNode: m.componentSetNode } : {}
11573
+ };
11574
+ } catch {
11575
+ return {};
11576
+ }
11577
+ }
11100
11578
  function emitBundleV1(opts) {
11101
11579
  const substituted = (opts.substitutedFamilies ?? []).length > 0;
11102
11580
  const parityFailed = new Set(opts.behaviors.filter((b) => b.id.startsWith("parity:") && !b.pass).map((b) => b.id.slice("parity:".length)));
@@ -11115,8 +11593,8 @@ function emitBundleV1(opts) {
11115
11593
  const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
11116
11594
  const parity = opts.behaviors.filter((b) => b.id.startsWith("parity:"));
11117
11595
  const contract = opts.behaviors.filter((b) => CONTRACT_CHECK_IDS.has(b.id));
11118
- const cssFiles = ["styles.css", "tokens.css"].map((f) => path32.join(opts.bundleDir, f)).filter((f) => existsSync26(f));
11119
- const families = cssFontFamilies(cssFiles.map((f) => readFileSync22(f, "utf8")).join("\n"));
11596
+ const cssFiles = ["styles.css", "tokens.css"].map((f) => path34.join(opts.bundleDir, f)).filter((f) => existsSync27(f));
11597
+ const families = cssFontFamilies(cssFiles.map((f) => readFileSync25(f, "utf8")).join("\n"));
11120
11598
  const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
11121
11599
  family: f.family,
11122
11600
  weight: f.weight,
@@ -11145,12 +11623,21 @@ function emitBundleV1(opts) {
11145
11623
  // resolvable via verify's --set override).
11146
11624
  path: (() => {
11147
11625
  const base = process.env["INIT_CWD"] ?? process.cwd();
11148
- const rel = path32.relative(base, opts.task.set);
11626
+ const rel = path34.relative(base, opts.task.set);
11149
11627
  return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
11150
11628
  })(),
11151
11629
  component: opts.componentName,
11152
11630
  ...opts.licenseNote !== void 0 ? { licenseNote: opts.licenseNote } : {},
11153
- hash: recordingSetHash(opts.task.set, opts.task.configs)
11631
+ hash: recordingSetHash(opts.task.set, opts.task.configs),
11632
+ // PROV-1: kit identity, carried through from the recording set.
11633
+ // Captured at plan time and previously dropped here, which left
11634
+ // a published bundle unable to name the kit it derives from —
11635
+ // and the portal's consent is scoped to exactly this file key
11636
+ // (ADR-018 §3), because the right to publish attaches to a kit.
11637
+ // Read defensively: a set planned before these fields existed,
11638
+ // or one built from a handed-in selection, legitimately has
11639
+ // neither, and absent must read as NOT RECORDED.
11640
+ ...kitIdentity(opts.task.set)
11154
11641
  },
11155
11642
  environment: { ...opts.environment, ...(opts.substitutedFamilies ?? []).length > 0 ? { substitutedFamilies: opts.substitutedFamilies } : {} },
11156
11643
  coverage: { recordedConfigs: statuses.length, latticeConfigs: lattice },
@@ -11173,25 +11660,25 @@ function emitBundleV1(opts) {
11173
11660
  })
11174
11661
  };
11175
11662
  const written = [];
11176
- const manifestPath2 = path32.join(opts.bundleDir, "component.json");
11177
- writeFileSync10(manifestPath2, `${JSON.stringify(manifest, null, 2)}
11663
+ const manifestPath2 = path34.join(opts.bundleDir, "component.json");
11664
+ writeFileSync11(manifestPath2, `${JSON.stringify(manifest, null, 2)}
11178
11665
  `);
11179
11666
  written.push(manifestPath2);
11180
- const stylesPath = path32.join(opts.bundleDir, "styles.css");
11181
- if (existsSync26(stylesPath)) {
11667
+ const stylesPath = path34.join(opts.bundleDir, "styles.css");
11668
+ if (existsSync27(stylesPath)) {
11182
11669
  const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
11183
- const current = readFileSync22(stylesPath, "utf8");
11670
+ const current = readFileSync25(stylesPath, "utf8");
11184
11671
  const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
11185
- writeFileSync10(stylesPath, `${comment}
11672
+ writeFileSync11(stylesPath, `${comment}
11186
11673
  ${stripped}`);
11187
11674
  written.push(stylesPath);
11188
11675
  }
11189
- const fontsCssPath = path32.join(opts.bundleDir, "fonts.css");
11190
- rmSync3(path32.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
11191
- rmSync3(fontsCssPath, { force: true });
11676
+ const fontsCssPath = path34.join(opts.bundleDir, "fonts.css");
11677
+ rmSync4(path34.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
11678
+ rmSync4(fontsCssPath, { force: true });
11192
11679
  const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
11193
11680
  if (fontsCss !== null) {
11194
- writeFileSync10(fontsCssPath, fontsCss);
11681
+ writeFileSync11(fontsCssPath, fontsCss);
11195
11682
  written.push(fontsCssPath);
11196
11683
  }
11197
11684
  const unrecorded = lattice === null ? null : Math.max(0, lattice - statuses.length);
@@ -11619,9 +12106,9 @@ DEALINGS IN THE FONT SOFTWARE.
11619
12106
  });
11620
12107
 
11621
12108
  // packages/generate/src/compose-pins.ts
11622
- import { createHash as createHash6 } from "node:crypto";
11623
- import { existsSync as existsSync27, readFileSync as readFileSync23, readdirSync as readdirSync11, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
11624
- import path33 from "node:path";
12109
+ import { createHash as createHash7 } from "node:crypto";
12110
+ import { existsSync as existsSync28, readFileSync as readFileSync26, readdirSync as readdirSync12, realpathSync as realpathSync3, statSync as statSync4 } from "node:fs";
12111
+ import path35 from "node:path";
11625
12112
  function bundleDirs(roots, depth = 4) {
11626
12113
  const found = [];
11627
12114
  const seen = /* @__PURE__ */ new Set();
@@ -11630,31 +12117,31 @@ function bundleDirs(roots, depth = 4) {
11630
12117
  try {
11631
12118
  key = realpathSync3(dir);
11632
12119
  } catch {
11633
- key = path33.resolve(dir);
12120
+ key = path35.resolve(dir);
11634
12121
  }
11635
12122
  if (seen.has(key)) return;
11636
12123
  seen.add(key);
11637
- if (existsSync27(path33.join(dir, "component.json"))) {
12124
+ if (existsSync28(path35.join(dir, "component.json"))) {
11638
12125
  found.push(key);
11639
12126
  return;
11640
12127
  }
11641
12128
  if (remaining === 0) return;
11642
12129
  let entries;
11643
12130
  try {
11644
- entries = readdirSync11(dir);
12131
+ entries = readdirSync12(dir);
11645
12132
  } catch {
11646
12133
  return;
11647
12134
  }
11648
12135
  for (const e of entries) {
11649
12136
  if (e === "node_modules" || e.startsWith(".")) continue;
11650
- const full = path33.join(dir, e);
12137
+ const full = path35.join(dir, e);
11651
12138
  try {
11652
- if (statSync3(full).isDirectory()) walk2(full, remaining - 1);
12139
+ if (statSync4(full).isDirectory()) walk2(full, remaining - 1);
11653
12140
  } catch {
11654
12141
  }
11655
12142
  }
11656
12143
  };
11657
- for (const r of roots) walk2(path33.resolve(r), depth);
12144
+ for (const r of roots) walk2(path35.resolve(r), depth);
11658
12145
  return found;
11659
12146
  }
11660
12147
  function composedPins(hostSet, libraryRoots) {
@@ -11673,7 +12160,7 @@ function composedPins(hostSet, libraryRoots) {
11673
12160
  let pinned = false;
11674
12161
  const failures = [];
11675
12162
  for (const rel of partnerRels) {
11676
- const partnerSet = path33.resolve(hostSet, rel);
12163
+ const partnerSet = path35.resolve(hostSet, rel);
11677
12164
  let partnerTask;
11678
12165
  let partnerManifest;
11679
12166
  try {
@@ -11702,7 +12189,7 @@ function composedPins(hostSet, libraryRoots) {
11702
12189
  const wantHash = recordingSetHash(partnerSet, partnerTask.configs);
11703
12190
  const matches = candidates.filter((dir) => {
11704
12191
  try {
11705
- const parsed = readBundleManifest(readFileSync23(path33.join(dir, "component.json"), "utf8"));
12192
+ const parsed = readBundleManifest(readFileSync26(path35.join(dir, "component.json"), "utf8"));
11706
12193
  return parsed.manifest?.provenance.recordingSet.hash === wantHash;
11707
12194
  } catch {
11708
12195
  return false;
@@ -11715,13 +12202,13 @@ function composedPins(hostSet, libraryRoots) {
11715
12202
  continue;
11716
12203
  }
11717
12204
  if (matches.length > 1) {
11718
- failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) => path33.basename(m)).join(", ")}) \u2014 ambiguous; remove or point --library away from the duplicates`);
12205
+ failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) => path35.basename(m)).join(", ")}) \u2014 ambiguous; remove or point --library away from the duplicates`);
11719
12206
  continue;
11720
12207
  }
11721
12208
  const bundleDir = matches[0];
11722
12209
  let manifest;
11723
12210
  try {
11724
- manifest = readBundleManifest(readFileSync23(path33.join(bundleDir, "component.json"), "utf8")).manifest;
12211
+ manifest = readBundleManifest(readFileSync26(path35.join(bundleDir, "component.json"), "utf8")).manifest;
11725
12212
  } catch {
11726
12213
  manifest = void 0;
11727
12214
  }
@@ -11738,8 +12225,8 @@ function composedPins(hostSet, libraryRoots) {
11738
12225
  const moduleFiles = [];
11739
12226
  let fileIssue;
11740
12227
  for (const name of [manifest.entry, "styles.css", "tokens.css", "fonts.css"]) {
11741
- const file = path33.join(bundleDir, name);
11742
- if (!existsSync27(file)) {
12228
+ const file = path35.join(bundleDir, name);
12229
+ if (!existsSync28(file)) {
11743
12230
  if (name === manifest.entry || name === "styles.css") {
11744
12231
  fileIssue = `${rel}: partner bundle is missing ${name}`;
11745
12232
  break;
@@ -11748,7 +12235,7 @@ function composedPins(hostSet, libraryRoots) {
11748
12235
  }
11749
12236
  let bytes;
11750
12237
  try {
11751
- bytes = readFileSync23(file);
12238
+ bytes = readFileSync26(file);
11752
12239
  } catch {
11753
12240
  fileIssue = `${rel}: partner file ${name} at ${bundleDir} is unreadable`;
11754
12241
  break;
@@ -11757,7 +12244,7 @@ function composedPins(hostSet, libraryRoots) {
11757
12244
  fileIssue = `${rel}: partner file ${name} exceeds the pin size cap (${bytes.byteLength} bytes)`;
11758
12245
  break;
11759
12246
  }
11760
- moduleFiles.push({ name, content: bytes.toString("utf8"), sha256: createHash6("sha256").update(bytes).digest("hex") });
12247
+ moduleFiles.push({ name, content: bytes.toString("utf8"), sha256: createHash7("sha256").update(bytes).digest("hex") });
11761
12248
  }
11762
12249
  if (fileIssue !== void 0) {
11763
12250
  failures.push(fileIssue);
@@ -11820,14 +12307,14 @@ function composedChecks(candidateDir, hostEntry, pins) {
11820
12307
  const checks = [];
11821
12308
  let entrySource = "";
11822
12309
  try {
11823
- entrySource = readFileSync23(path33.join(candidateDir, hostEntry), "utf8");
12310
+ entrySource = readFileSync26(path35.join(candidateDir, hostEntry), "utf8");
11824
12311
  } catch {
11825
12312
  }
11826
- const candidateRoot = path33.resolve(candidateDir);
12313
+ const candidateRoot = path35.resolve(candidateDir);
11827
12314
  for (const pin of pins) {
11828
12315
  const dir = composedModuleDir(pin.partnerName);
11829
- const resolvedDir = path33.resolve(candidateDir, dir);
11830
- if (!resolvedDir.startsWith(candidateRoot + path33.sep)) {
12316
+ const resolvedDir = path35.resolve(candidateDir, dir);
12317
+ if (!resolvedDir.startsWith(candidateRoot + path35.sep)) {
11831
12318
  checks.push({ id: `composition:${pin.pairKey}:module-verbatim`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
11832
12319
  checks.push({ id: `composition:${pin.pairKey}:imported`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
11833
12320
  continue;
@@ -11838,12 +12325,12 @@ function composedChecks(candidateDir, hostEntry, pins) {
11838
12325
  wrong.push(`${f.name} refused (not a plain path segment)`);
11839
12326
  continue;
11840
12327
  }
11841
- const target = path33.join(candidateDir, dir, f.name);
11842
- if (!existsSync27(target)) {
12328
+ const target = path35.join(candidateDir, dir, f.name);
12329
+ if (!existsSync28(target)) {
11843
12330
  wrong.push(`${f.name} missing`);
11844
12331
  continue;
11845
12332
  }
11846
- const sha = createHash6("sha256").update(readFileSync23(target)).digest("hex");
12333
+ const sha = createHash7("sha256").update(readFileSync26(target)).digest("hex");
11847
12334
  if (sha !== f.sha256) wrong.push(`${f.name} differs from the pinned partner bytes`);
11848
12335
  }
11849
12336
  checks.push({
@@ -11868,10 +12355,10 @@ function rootClassesFor(emission, nodeId) {
11868
12355
  }
11869
12356
  function regionOverrides(hostSet, partnerSet, instances) {
11870
12357
  const read = (setDir, rep) => {
11871
- const f = path33.join(setDir, rep, "get_design_context.json");
11872
- if (!existsSync27(f)) return void 0;
12358
+ const f = path35.join(setDir, rep, "get_design_context.json");
12359
+ if (!existsSync28(f)) return void 0;
11873
12360
  try {
11874
- return envelopeFirstTextPart(JSON.parse(readFileSync23(f, "utf8")));
12361
+ return envelopeFirstTextPart(JSON.parse(readFileSync26(f, "utf8")));
11875
12362
  } catch {
11876
12363
  return void 0;
11877
12364
  }
@@ -11901,7 +12388,7 @@ var init_compose_pins = __esm({
11901
12388
  init_src4();
11902
12389
  init_brief();
11903
12390
  init_bundle_emit();
11904
- composedModuleDir = (partnerName) => path33.posix.join("composed", partnerName);
12391
+ composedModuleDir = (partnerName) => path35.posix.join("composed", partnerName);
11905
12392
  safeSegment = (s) => /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(s) && !s.includes("..");
11906
12393
  MAX_PINNED_FILE_BYTES = 1024 * 1024;
11907
12394
  CONTEXT_LAYOUT_TOKENS = /* @__PURE__ */ new Set(["shrink-0", "grow", "flex-1", "basis-0", "self-stretch", "w-full", "h-full"]);
@@ -11909,8 +12396,8 @@ var init_compose_pins = __esm({
11909
12396
  });
11910
12397
 
11911
12398
  // packages/generate/src/motion.ts
11912
- import { existsSync as existsSync28, readFileSync as readFileSync24, readdirSync as readdirSync12, statSync as statSync4 } from "node:fs";
11913
- import path34 from "node:path";
12399
+ import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync13, statSync as statSync5 } from "node:fs";
12400
+ import path36 from "node:path";
11914
12401
  function springProgress(u, bounce) {
11915
12402
  const decay = Math.log(100);
11916
12403
  if (bounce <= 0) {
@@ -11981,10 +12468,10 @@ function reportsNoMotion(text) {
11981
12468
  });
11982
12469
  }
11983
12470
  function motionTruthFor(setDir) {
11984
- const file = path34.join(setDir, "get_motion_context.json");
11985
- if (existsSync28(file) && usableEnvelope(file, "get_motion_context").ok) {
12471
+ const file = path36.join(setDir, "get_motion_context.json");
12472
+ if (existsSync29(file) && usableEnvelope(file, "get_motion_context").ok) {
11986
12473
  try {
11987
- const text = envelopeTextContent(JSON.parse(readFileSync24(file, "utf8")));
12474
+ const text = envelopeTextContent(JSON.parse(readFileSync27(file, "utf8")));
11988
12475
  if (text.trim() === "") return { state: "recorded-empty" };
11989
12476
  return reportsNoMotion(text) ? { state: "recorded-no-motion", text } : { state: "recorded", text };
11990
12477
  } catch {
@@ -11997,21 +12484,21 @@ function motionTruthFor(setDir) {
11997
12484
  }
11998
12485
  }
11999
12486
  function motionDisclosure(bundleDir, setDir) {
12000
- const sheets = ["styles.css", "tokens.css"].map((f) => path34.join(bundleDir, f));
12001
- const composedRoot = path34.join(bundleDir, "composed");
12487
+ const sheets = ["styles.css", "tokens.css"].map((f) => path36.join(bundleDir, f));
12488
+ const composedRoot = path36.join(bundleDir, "composed");
12002
12489
  try {
12003
- for (const entry of readdirSync12(composedRoot).sort()) {
12004
- const dir = path34.join(composedRoot, entry);
12490
+ for (const entry of readdirSync13(composedRoot).sort()) {
12491
+ const dir = path36.join(composedRoot, entry);
12005
12492
  try {
12006
- if (!statSync4(dir).isDirectory()) continue;
12493
+ if (!statSync5(dir).isDirectory()) continue;
12007
12494
  } catch {
12008
12495
  continue;
12009
12496
  }
12010
- sheets.push(path34.join(dir, "styles.css"), path34.join(dir, "tokens.css"));
12497
+ sheets.push(path36.join(dir, "styles.css"), path36.join(dir, "tokens.css"));
12011
12498
  }
12012
12499
  } catch {
12013
12500
  }
12014
- const css = sheets.filter((f) => existsSync28(f)).map((f) => readFileSync24(f, "utf8")).join("\n");
12501
+ const css = sheets.filter((f) => existsSync29(f)).map((f) => readFileSync27(f, "utf8")).join("\n");
12015
12502
  if (!MOTION_CSS_PATTERN.test(scannableCss(css))) return { present: false };
12016
12503
  return {
12017
12504
  present: true,
@@ -12378,12 +12865,12 @@ var init_components = __esm({
12378
12865
 
12379
12866
  // packages/generate/src/codebase/walk.ts
12380
12867
  import fs2 from "node:fs";
12381
- import path35 from "node:path";
12868
+ import path37 from "node:path";
12382
12869
  function resolvedPathIsExcluded(real, roots) {
12383
- if (isNeverRead(path35.basename(real))) return true;
12870
+ if (isNeverRead(path37.basename(real))) return true;
12384
12871
  for (const root of roots) {
12385
- if (real !== root && !real.startsWith(root + path35.sep)) continue;
12386
- for (const segment of path35.relative(root, real).split(path35.sep).slice(0, -1)) {
12872
+ if (real !== root && !real.startsWith(root + path37.sep)) continue;
12873
+ for (const segment of path37.relative(root, real).split(path37.sep).slice(0, -1)) {
12387
12874
  if (segment.startsWith(".") || EXCLUDED_DIRS.has(segment)) return true;
12388
12875
  }
12389
12876
  }
@@ -12397,7 +12884,7 @@ function containedRealpath(abs, roots) {
12397
12884
  return null;
12398
12885
  }
12399
12886
  for (const root of roots) {
12400
- if (real === root || real.startsWith(root + path35.sep)) return real;
12887
+ if (real === root || real.startsWith(root + path37.sep)) return real;
12401
12888
  }
12402
12889
  return null;
12403
12890
  }
@@ -12430,7 +12917,7 @@ function walkRepo(roots, limits, accept) {
12430
12917
  continue;
12431
12918
  }
12432
12919
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
12433
- const abs = path35.join(frame.dir, entry.name);
12920
+ const abs = path37.join(frame.dir, entry.name);
12434
12921
  if (EXCLUDED_DIRS.has(entry.name)) continue;
12435
12922
  if (isNeverRead(entry.name)) continue;
12436
12923
  const real = containedRealpath(abs, realRoots);
@@ -12518,18 +13005,18 @@ var init_walk = __esm({
12518
13005
  /^\.netrc$/i
12519
13006
  ];
12520
13007
  isNeverRead = (basename) => NEVER_READ.some((re) => re.test(basename));
12521
- toRel = (root, abs) => path35.relative(root, abs).split(path35.sep).join(path35.posix.sep);
13008
+ toRel = (root, abs) => path37.relative(root, abs).split(path37.sep).join(path37.posix.sep);
12522
13009
  }
12523
13010
  });
12524
13011
 
12525
13012
  // packages/generate/src/codebase/scan.ts
12526
13013
  import crypto2 from "node:crypto";
12527
13014
  import fs3 from "node:fs";
12528
- import path36 from "node:path";
13015
+ import path38 from "node:path";
12529
13016
  import postcss3 from "postcss";
12530
13017
  function scanCodebase(options) {
12531
13018
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
12532
- const roots = options.roots.map((r) => path36.resolve(r));
13019
+ const roots = options.roots.map((r) => path38.resolve(r));
12533
13020
  const walk2 = walkRepo(
12534
13021
  roots,
12535
13022
  {
@@ -12549,7 +13036,7 @@ function scanCodebase(options) {
12549
13036
  let bytesRead = 0;
12550
13037
  let filesRead = 0;
12551
13038
  for (const file of walk2.files) {
12552
- const base = path36.posix.basename(file.rel);
13039
+ const base = path38.posix.basename(file.rel);
12553
13040
  configFiles.add(file.rel);
12554
13041
  if (/^tailwind\.config\./.test(base) || file.rel === "babel.config.js") continue;
12555
13042
  const text = readTextFile(file.abs);
@@ -12565,7 +13052,7 @@ function scanCodebase(options) {
12565
13052
  }
12566
13053
  const css = extractCssCustomProperties(cssFiles.filter((f) => !f.rel.includes("..")));
12567
13054
  const components = scanComponents(componentFiles);
12568
- const packages = manifests.filter((m) => path36.posix.basename(m.rel) === "package.json");
13055
+ const packages = manifests.filter((m) => path38.posix.basename(m.rel) === "package.json");
12569
13056
  const styling = detectStyling(configFiles, cssFiles, componentFiles, manifests);
12570
13057
  const classNameStyle = representativeClassNames(cssFiles, css.unparsed.length);
12571
13058
  const disclosures = buildDisclosures(
@@ -12608,13 +13095,13 @@ function scanCodebase(options) {
12608
13095
  },
12609
13096
  components: {
12610
13097
  entries: components.entries.slice(0, PROFILE_LIMITS.maxComponents),
12611
- fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path36.posix.basename(f.rel)))),
13098
+ fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path38.posix.basename(f.rel)))),
12612
13099
  directoryLayout: buildHistogram(componentFiles.map((f) => classifyDirectoryLayout(f.rel))),
12613
13100
  exportStyle: buildHistogram(components.entries.map((e) => e.exportStyle)),
12614
13101
  classNameStyle,
12615
13102
  colocation: buildHistogram(collectColocation(componentFiles, cssFiles)),
12616
13103
  barrelFiles: componentFiles.filter(
12617
- (f) => /^index\.[tj]sx?$/.test(path36.posix.basename(f.rel)) && isReExportOnly(f.text)
13104
+ (f) => /^index\.[tj]sx?$/.test(path38.posix.basename(f.rel)) && isReExportOnly(f.text)
12618
13105
  ).length,
12619
13106
  refForwarding: {
12620
13107
  forwardRef: componentFiles.filter((f) => /\bforwardRef\s*[(<]/.test(f.text)).length,
@@ -12637,7 +13124,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
12637
13124
  };
12638
13125
  const deps = /* @__PURE__ */ new Map();
12639
13126
  for (const manifest of manifests) {
12640
- if (path36.posix.basename(manifest.rel) !== "package.json") continue;
13127
+ if (path38.posix.basename(manifest.rel) !== "package.json") continue;
12641
13128
  try {
12642
13129
  const parsed = JSON.parse(manifest.text);
12643
13130
  for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
@@ -12649,7 +13136,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
12649
13136
  }
12650
13137
  }
12651
13138
  for (const cfg of configFiles) {
12652
- const base = path36.posix.basename(cfg);
13139
+ const base = path38.posix.basename(cfg);
12653
13140
  if (/^tailwind\.config\./.test(base)) add("tailwind-v3", "file", cfg);
12654
13141
  if (base === "components.json") add("shadcn-style", "file", cfg);
12655
13142
  }
@@ -12707,8 +13194,8 @@ function collectClassNames(cssFiles) {
12707
13194
  return [...distinct].sort().map(classifyClassName);
12708
13195
  }
12709
13196
  function classifyDirectoryLayout(rel) {
12710
- const base = path36.posix.basename(rel).replace(/\.[^.]+$/, "");
12711
- const dir = path36.posix.basename(path36.posix.dirname(rel));
13197
+ const base = path38.posix.basename(rel).replace(/\.[^.]+$/, "");
13198
+ const dir = path38.posix.basename(path38.posix.dirname(rel));
12712
13199
  if (base === "index") return "component-dir";
12713
13200
  if (base === dir) return "component-dir";
12714
13201
  return "flat-file";
@@ -12732,7 +13219,7 @@ function detectFormatting(manifests, componentFiles) {
12732
13219
  (a, b) => a.rel.split("/").length - b.rel.split("/").length || a.rel.localeCompare(b.rel)
12733
13220
  );
12734
13221
  for (const manifest of byDepth) {
12735
- const base = path36.posix.basename(manifest.rel);
13222
+ const base = path38.posix.basename(manifest.rel);
12736
13223
  if (!/^\.prettierrc/.test(base) && base !== "package.json") continue;
12737
13224
  try {
12738
13225
  const parsed = JSON.parse(manifest.text);
@@ -12750,7 +13237,7 @@ function detectFormatting(manifests, componentFiles) {
12750
13237
  }
12751
13238
  }
12752
13239
  for (const manifest of byDepth) {
12753
- if (path36.posix.basename(manifest.rel) !== ".editorconfig") continue;
13240
+ if (path38.posix.basename(manifest.rel) !== ".editorconfig") continue;
12754
13241
  const style = /indent_style\s*=\s*(tab|space)/.exec(manifest.text)?.[1];
12755
13242
  const width = /indent_size\s*=\s*(\d+)/.exec(manifest.text)?.[1];
12756
13243
  if (style || width) {
@@ -12821,12 +13308,12 @@ function buildDisclosures(detected, css, cappedOut, unrepresentativeClassNames)
12821
13308
  return out;
12822
13309
  }
12823
13310
  function outPathIsGitIgnored(outPath) {
12824
- const dir = path36.dirname(outPath);
13311
+ const dir = path38.dirname(outPath);
12825
13312
  try {
12826
- const ignoreFile = path36.join(path36.dirname(dir), ".gitignore");
13313
+ const ignoreFile = path38.join(path38.dirname(dir), ".gitignore");
12827
13314
  if (!fs3.existsSync(ignoreFile)) return false;
12828
13315
  const patterns = fs3.readFileSync(ignoreFile, "utf8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
12829
- const base = path36.basename(dir);
13316
+ const base = path38.basename(dir);
12830
13317
  return patterns.some((p) => p === base || p === `${base}/` || p === `/${base}` || p === `/${base}/`);
12831
13318
  } catch {
12832
13319
  return false;
@@ -12973,8 +13460,8 @@ __export(profile_exports, {
12973
13460
  PROFILE_DESCRIPTION: () => PROFILE_DESCRIPTION,
12974
13461
  runProfile: () => runProfile
12975
13462
  });
12976
- import { closeSync, constants, existsSync as existsSync29, mkdirSync as mkdirSync8, openSync, realpathSync as realpathSync4, writeFileSync as writeFileSync11 } from "node:fs";
12977
- import path37 from "node:path";
13463
+ import { closeSync, constants, existsSync as existsSync30, mkdirSync as mkdirSync9, openSync, realpathSync as realpathSync4, writeFileSync as writeFileSync12 } from "node:fs";
13464
+ import path39 from "node:path";
12978
13465
  function escapesScanRoot(outPath, scanRoot) {
12979
13466
  const resolveExisting = (target) => {
12980
13467
  let cursor = target;
@@ -12982,23 +13469,23 @@ function escapesScanRoot(outPath, scanRoot) {
12982
13469
  try {
12983
13470
  return realpathSync4(cursor);
12984
13471
  } catch {
12985
- const parent = path37.dirname(cursor);
13472
+ const parent = path39.dirname(cursor);
12986
13473
  if (parent === cursor) return cursor;
12987
13474
  cursor = parent;
12988
13475
  }
12989
13476
  }
12990
13477
  };
12991
13478
  const root = resolveExisting(scanRoot);
12992
- const dir = resolveExisting(path37.dirname(outPath));
12993
- return dir !== root && !dir.startsWith(root + path37.sep);
13479
+ const dir = resolveExisting(path39.dirname(outPath));
13480
+ return dir !== root && !dir.startsWith(root + path39.sep);
12994
13481
  }
12995
13482
  function runProfile(options) {
12996
13483
  if (options.describe) {
12997
13484
  printDescription(PROFILE_DESCRIPTION);
12998
13485
  return;
12999
13486
  }
13000
- const dir = path37.resolve(options.dir ?? ".");
13001
- if (!existsSync29(dir)) {
13487
+ const dir = path39.resolve(options.dir ?? ".");
13488
+ if (!existsSync30(dir)) {
13002
13489
  fail(options, ExitCode.InputValidation, {
13003
13490
  error: `no such directory: ${dir}`,
13004
13491
  code: "profile_dir_missing",
@@ -13006,7 +13493,7 @@ function runProfile(options) {
13006
13493
  });
13007
13494
  }
13008
13495
  const profile = scanCodebase({ roots: [dir], ...options.now ? { now: options.now } : {} });
13009
- const outPath = path37.resolve(options.out ?? path37.join(dir, "tendril-out", "codebase-profile.json"));
13496
+ const outPath = path39.resolve(options.out ?? path39.join(dir, "tendril-out", "codebase-profile.json"));
13010
13497
  if (!options.dryRun) {
13011
13498
  if (options.out === void 0 && escapesScanRoot(outPath, dir)) {
13012
13499
  fail(options, ExitCode.InputValidation, {
@@ -13015,10 +13502,10 @@ function runProfile(options) {
13015
13502
  remediation: `\`tendril-out\` in that project is a symlink pointing outside it, so writing the profile there could overwrite an unrelated file. Remove the symlink, or choose an explicit destination: \`${tendrilCommand(`profile --dir ${quoteArg(dir)} --out ./codebase-profile.json`)}\`.`
13016
13503
  });
13017
13504
  }
13018
- mkdirSync8(path37.dirname(outPath), { recursive: true });
13505
+ mkdirSync9(path39.dirname(outPath), { recursive: true });
13019
13506
  const handle = openSync(outPath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 420);
13020
13507
  try {
13021
- writeFileSync11(handle, `${JSON.stringify(profile, null, 2)}
13508
+ writeFileSync12(handle, `${JSON.stringify(profile, null, 2)}
13022
13509
  `, "utf8");
13023
13510
  } finally {
13024
13511
  closeSync(handle);
@@ -13080,7 +13567,7 @@ Written to ${outPath}
13080
13567
  `);
13081
13568
  if (!ignored) {
13082
13569
  process.stdout.write(
13083
- ` NOTE: ${path37.basename(path37.dirname(outPath))}/ is not gitignored here \u2014 add it to .gitignore, or this profile will show up in your next commit.
13570
+ ` NOTE: ${path39.basename(path39.dirname(outPath))}/ is not gitignored here \u2014 add it to .gitignore, or this profile will show up in your next commit.
13084
13571
  `
13085
13572
  );
13086
13573
  }
@@ -13219,11 +13706,11 @@ __export(compose_exports, {
13219
13706
  compositionPairsFor: () => compositionPairsFor,
13220
13707
  runCompose: () => runCompose
13221
13708
  });
13222
- import { createHash as createHash7 } from "node:crypto";
13223
- import { existsSync as existsSync30, readFileSync as readFileSync25 } from "node:fs";
13224
- import path38 from "node:path";
13709
+ import { createHash as createHash8 } from "node:crypto";
13710
+ import { existsSync as existsSync31, readFileSync as readFileSync28 } from "node:fs";
13711
+ import path40 from "node:path";
13225
13712
  function compositionPairsFor(hostSet, roots) {
13226
- const scanRoots = [.../* @__PURE__ */ new Set([...roots, path38.dirname(hostSet)])];
13713
+ const scanRoots = [.../* @__PURE__ */ new Set([...roots, path40.dirname(hostSet)])];
13227
13714
  const edges = composeReport(buildComposeIndex(scanRoots));
13228
13715
  const pairs = substitutionPairs(edges, hostSet);
13229
13716
  const { raw } = readManifestFile(hostSet);
@@ -13255,7 +13742,7 @@ function substitutionPairs(edges, hostSet) {
13255
13742
  });
13256
13743
  }
13257
13744
  const pair = pairs.get(key);
13258
- const poseDisplay = e.pose.reps.map((r) => `${path38.basename(r.dir)}:${r.slug}`).join(", ");
13745
+ const poseDisplay = e.pose.reps.map((r) => `${path40.basename(r.dir)}:${r.slug}`).join(", ");
13259
13746
  pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
13260
13747
  for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
13261
13748
  }
@@ -13267,7 +13754,7 @@ function runCompose(flags) {
13267
13754
  return;
13268
13755
  }
13269
13756
  const base = process.env["INIT_CWD"] ?? process.cwd();
13270
- const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path38.resolve(base, d)) : [base];
13757
+ const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path40.resolve(base, d)) : [base];
13271
13758
  if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
13272
13759
  fail(flags, ExitCode.InputValidation, {
13273
13760
  error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
@@ -13276,7 +13763,7 @@ function runCompose(flags) {
13276
13763
  });
13277
13764
  }
13278
13765
  if (flags.set !== void 0) {
13279
- runComposeConfirm(flags, path38.resolve(base, flags.set), roots);
13766
+ runComposeConfirm(flags, path40.resolve(base, flags.set), roots);
13280
13767
  return;
13281
13768
  }
13282
13769
  const index = buildComposeIndex(roots);
@@ -13294,7 +13781,7 @@ function runCompose(flags) {
13294
13781
  }
13295
13782
  let lastHost = "";
13296
13783
  for (const e of edges) {
13297
- const host = `${path38.basename(e.hostSet)}`;
13784
+ const host = `${path40.basename(e.hostSet)}`;
13298
13785
  if (host !== lastHost) {
13299
13786
  process.stdout.write(`
13300
13787
  ${host}
@@ -13302,7 +13789,7 @@ ${host}
13302
13789
  lastHost = host;
13303
13790
  }
13304
13791
  const partners = e.partners.map((p) => p.displayName).join(" / ") || "\u2014";
13305
- const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path38.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
13792
+ const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path40.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
13306
13793
  process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
13307
13794
  `);
13308
13795
  for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
@@ -13314,14 +13801,14 @@ ${NOTE}
13314
13801
  });
13315
13802
  }
13316
13803
  function runComposeConfirm(flags, hostSet, roots) {
13317
- if (!existsSync30(path38.join(hostSet, "recording-set.json"))) {
13804
+ if (!existsSync31(path40.join(hostSet, "recording-set.json"))) {
13318
13805
  fail(flags, ExitCode.InputValidation, {
13319
13806
  error: `no recording-set.json in ${hostSet}`,
13320
13807
  code: "no-recording-set",
13321
13808
  remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
13322
13809
  });
13323
13810
  }
13324
- const scanRoots = [.../* @__PURE__ */ new Set([...roots, path38.dirname(hostSet)])];
13811
+ const scanRoots = [.../* @__PURE__ */ new Set([...roots, path40.dirname(hostSet)])];
13325
13812
  const index = buildComposeIndex(scanRoots);
13326
13813
  const edges = composeReport(index);
13327
13814
  const pairs = substitutionPairs(edges, hostSet);
@@ -13404,7 +13891,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
13404
13891
  // full recording-set hash join lands with pin authoring, where
13405
13892
  // task configs exist.)
13406
13893
  manifestSha256: Object.fromEntries(
13407
- p.partnerDirs.map((d) => [path38.relative(hostSet, d), createHash7("sha256").update(readFileSync25(path38.join(d, "recording-set.json"))).digest("hex")])
13894
+ p.partnerDirs.map((d) => [path40.relative(hostSet, d), createHash8("sha256").update(readFileSync28(path40.join(d, "recording-set.json"))).digest("hex")])
13408
13895
  )
13409
13896
  },
13410
13897
  // Schema shape ONLY (review: the display-side poseDisplay field
@@ -13483,10 +13970,10 @@ __export(record_exports, {
13483
13970
  runRecordPlan: () => runRecordPlan,
13484
13971
  runRecordStatus: () => runRecordStatus
13485
13972
  });
13486
- import { existsSync as existsSync31, mkdtempSync as mkdtempSync2, readFileSync as readFileSync26, readdirSync as readdirSync13 } from "node:fs";
13487
- import os6 from "node:os";
13488
- import path39 from "node:path";
13489
- import { writeFileSync as writeFileSync12 } from "node:fs";
13973
+ import { existsSync as existsSync32, mkdtempSync as mkdtempSync2, readFileSync as readFileSync29, readdirSync as readdirSync14 } from "node:fs";
13974
+ import os7 from "node:os";
13975
+ import path41 from "node:path";
13976
+ import { writeFileSync as writeFileSync13 } from "node:fs";
13490
13977
  function recordsInteractionState(reports) {
13491
13978
  const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
13492
13979
  return reports.some((r) => evident(r.axis) || r.domain.some(evident));
@@ -13508,7 +13995,7 @@ function interactionDisclosure(component, reports) {
13508
13995
  };
13509
13996
  }
13510
13997
  function symbolsFromMetadataEnvelope(file, sourceFrame) {
13511
- const env = JSON.parse(readFileSync26(file, "utf8"));
13998
+ const env = JSON.parse(readFileSync29(file, "utf8"));
13512
13999
  const text = env.content.map((c) => c.text ?? "").join("\n");
13513
14000
  const symbols = [];
13514
14001
  const walk2 = (node, ancestor) => {
@@ -13566,8 +14053,8 @@ function runRecordPlan(opts) {
13566
14053
  if (rawFile !== void 0) {
13567
14054
  try {
13568
14055
  const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
13569
- const tmp = path39.join(mkdtempSync2(path39.join(os6.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
13570
- writeFileSync12(tmp, JSON.stringify(envelope));
14056
+ const tmp = path41.join(mkdtempSync2(path41.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
14057
+ writeFileSync13(tmp, JSON.stringify(envelope));
13571
14058
  metadataEntries.push({ file: tmp });
13572
14059
  } catch (err) {
13573
14060
  fail(opts, ExitCode.InputValidation, {
@@ -13588,7 +14075,7 @@ function runRecordPlan(opts) {
13588
14075
  let metadataTruncated = false;
13589
14076
  for (const { file, frame } of metadataEntries) {
13590
14077
  try {
13591
- const parsed = symbolsFromMetadataEnvelope(path39.resolve(file), frame);
14078
+ const parsed = symbolsFromMetadataEnvelope(path41.resolve(file), frame);
13592
14079
  symbols.push(...parsed.symbols);
13593
14080
  if (parsed.truncated) metadataTruncated = true;
13594
14081
  } catch (err) {
@@ -13622,7 +14109,7 @@ function runRecordPlan(opts) {
13622
14109
  if (symbols.length === 0) {
13623
14110
  const leads = metadataEntries.flatMap(({ file }) => {
13624
14111
  try {
13625
- const env = JSON.parse(readFileSync26(path39.resolve(file), "utf8"));
14112
+ const env = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
13626
14113
  return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
13627
14114
  } catch {
13628
14115
  return [];
@@ -13715,7 +14202,7 @@ function runRecordPlan(opts) {
13715
14202
  // a CLI flag and the MCP surface has no parameter for it.
13716
14203
  decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
13717
14204
  text: "Record a reduced pose set (anchor + one-factor sweeps + conflict crosses) instead of the full variant matrix. This buys calls with coverage \u2014 sampling is blind to multi-axis interactions and the report discloses the poses it skipped \u2014 so only the user may accept that trade. There is no tool parameter for it; the user runs it themselves in their own terminal, before any pose is recorded.",
13718
- userRuns: [`rm ${quoteArg(path39.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
14205
+ userRuns: [`rm ${quoteArg(path41.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
13719
14206
  },
13720
14207
  {
13721
14208
  id: "larger-allowance",
@@ -13913,7 +14400,7 @@ function runRecordNext(opts) {
13913
14400
  const progress = payload["progress"];
13914
14401
  process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
13915
14402
  \u2192 ${payload["note"]}
13916
- \u2192 then: ${tendrilCommand(`record ingest --set ${path39.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
14403
+ \u2192 then: ${tendrilCommand(`record ingest --set ${path41.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
13917
14404
  `);
13918
14405
  });
13919
14406
  }
@@ -13987,7 +14474,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
13987
14474
  const skipped = [];
13988
14475
  const failed = [];
13989
14476
  for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
13990
- if (existsSync31(path39.join(setDir, rep, name))) {
14477
+ if (existsSync32(path41.join(setDir, rep, name))) {
13991
14478
  skipped.push(name);
13992
14479
  continue;
13993
14480
  }
@@ -14009,16 +14496,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14009
14496
  }
14010
14497
  function rawEnvelopeFromFile(file, parts) {
14011
14498
  if (parts) {
14012
- const blocks = JSON.parse(readFileSync26(path39.resolve(file), "utf8"));
14499
+ const blocks = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
14013
14500
  if (!Array.isArray(blocks) || blocks.length === 0 || blocks.some((p) => typeof p !== "string")) throw new Error("parts file must be a non-empty JSON array of strings");
14014
14501
  return { content: blocks.map((text) => ({ type: "text", text })) };
14015
14502
  }
14016
- return { content: [{ type: "text", text: readFileSync26(path39.resolve(file), "utf8") }] };
14503
+ return { content: [{ type: "text", text: readFileSync29(path41.resolve(file), "utf8") }] };
14017
14504
  }
14018
14505
  async function runRecordIngest(opts) {
14019
14506
  let payload;
14020
14507
  try {
14021
- payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync26(path39.resolve(opts.file), "utf8"));
14508
+ payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync29(path41.resolve(opts.file), "utf8"));
14022
14509
  } catch (err) {
14023
14510
  fail(opts, ExitCode.InputValidation, {
14024
14511
  error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
@@ -14030,7 +14517,7 @@ async function runRecordIngest(opts) {
14030
14517
  fail(opts, ExitCode.InputValidation, {
14031
14518
  error: "--raw is for text tool responses; screenshots are binary",
14032
14519
  code: "envelope-invalid",
14033
- remediation: `Use \`${tendrilCommand(`record fetch --set ${path39.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14520
+ remediation: `Use \`${tendrilCommand(`record fetch --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14034
14521
  });
14035
14522
  }
14036
14523
  if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
@@ -14050,7 +14537,7 @@ async function runRecordIngest(opts) {
14050
14537
  remediation: REINGEST_GUIDANCE
14051
14538
  });
14052
14539
  }
14053
- writeFileSync12(path39.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14540
+ writeFileSync13(path41.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14054
14541
  `);
14055
14542
  emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14056
14543
  process.stdout.write("set-level get_variable_defs ingested\n");
@@ -14074,7 +14561,7 @@ async function runRecordIngest(opts) {
14074
14561
  remediation: REINGEST_GUIDANCE
14075
14562
  });
14076
14563
  }
14077
- writeFileSync12(path39.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14564
+ writeFileSync13(path41.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14078
14565
  `);
14079
14566
  emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14080
14567
  process.stdout.write("set-level get_motion_context ingested\n");
@@ -14090,7 +14577,7 @@ async function runRecordIngest(opts) {
14090
14577
  if (assets !== void 0) {
14091
14578
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14092
14579
  `);
14093
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path39.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14580
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14094
14581
  `);
14095
14582
  }
14096
14583
  });
@@ -14163,15 +14650,15 @@ async function runRecordIngestRep(opts) {
14163
14650
  if (assets !== void 0) {
14164
14651
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14165
14652
  `);
14166
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path39.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14653
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14167
14654
  `);
14168
14655
  }
14169
14656
  });
14170
14657
  }
14171
14658
  function runRecordAsset(opts) {
14172
14659
  if (opts.dir !== void 0) {
14173
- const dir = path39.resolve(opts.dir);
14174
- const names = readdirSync13(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
14660
+ const dir = path41.resolve(opts.dir);
14661
+ const names = readdirSync14(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
14175
14662
  if (names.length === 0) {
14176
14663
  fail(opts, ExitCode.InputValidation, {
14177
14664
  error: `no asset-*.<ext> files found in ${dir}`,
@@ -14182,7 +14669,7 @@ function runRecordAsset(opts) {
14182
14669
  const ingested = [];
14183
14670
  try {
14184
14671
  for (const name of names) {
14185
- ingestAsset(opts.setDir, opts.rep, name, readFileSync26(path39.join(dir, name)));
14672
+ ingestAsset(opts.setDir, opts.rep, name, readFileSync29(path41.join(dir, name)));
14186
14673
  ingested.push(name);
14187
14674
  }
14188
14675
  } catch (err) {
@@ -14202,11 +14689,11 @@ function runRecordAsset(opts) {
14202
14689
  fail(opts, ExitCode.InputValidation, {
14203
14690
  error: "pass --name and --file for a single asset, or --dir for a batch",
14204
14691
  code: "asset-rejected",
14205
- remediation: tendrilCommand(`record asset --set ${path39.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14692
+ remediation: tendrilCommand(`record asset --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14206
14693
  });
14207
14694
  }
14208
14695
  try {
14209
- ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync26(path39.resolve(opts.file)));
14696
+ ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync29(path41.resolve(opts.file)));
14210
14697
  emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
14211
14698
  process.stdout.write(`ingested ${opts.rep}/${opts.name}
14212
14699
  `);
@@ -14223,8 +14710,8 @@ function runRecordStatus(opts) {
14223
14710
  const status = sessionStatus(opts.setDir);
14224
14711
  const composition = (() => {
14225
14712
  try {
14226
- const setDir = path39.resolve(opts.setDir);
14227
- const { open, standing, invalid } = compositionPairsFor(setDir, [path39.dirname(setDir)]);
14713
+ const setDir = path41.resolve(opts.setDir);
14714
+ const { open, standing, invalid } = compositionPairsFor(setDir, [path41.dirname(setDir)]);
14228
14715
  return {
14229
14716
  openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
14230
14717
  confirmed: standing.filter((s) => s.status === "confirmed").length,
@@ -14245,7 +14732,7 @@ function runRecordStatus(opts) {
14245
14732
  }
14246
14733
  }
14247
14734
  process.stdout.write(
14248
- status.motion.recorded ? motionTruthFor(path39.resolve(opts.setDir)).state === "recorded-no-motion" ? "MOTION set-level motion context recorded \u2014 the response reports NO motion data (no keyframe tracks, no snippets); briefs prescribe default doctrine and say so. This is the instrument's answer, not proof the design has no transitions\n" : status.motion.asked ? "MOTION set-level motion context recorded\n" : "MOTION set-level motion context recorded (ingested onto a set that predates the obligation \u2014 briefs will quote it as recorded truth)\n" : status.motion.asked ? status.motion.invalid !== void 0 ? `MOTION set-level motion file is UNUSABLE (${status.motion.invalid}) \u2014 re-record it via \`record next\`
14735
+ status.motion.recorded ? motionTruthFor(path41.resolve(opts.setDir)).state === "recorded-no-motion" ? "MOTION set-level motion context recorded \u2014 the response reports NO motion data (no keyframe tracks, no snippets); briefs prescribe default doctrine and say so. This is the instrument's answer, not proof the design has no transitions\n" : status.motion.asked ? "MOTION set-level motion context recorded\n" : "MOTION set-level motion context recorded (ingested onto a set that predates the obligation \u2014 briefs will quote it as recorded truth)\n" : status.motion.asked ? status.motion.invalid !== void 0 ? `MOTION set-level motion file is UNUSABLE (${status.motion.invalid}) \u2014 re-record it via \`record next\`
14249
14736
  ` : "MOTION set-level motion context not yet recorded \u2014 `record next` names the call once the reps and token map are done\n" : "MOTION never asked \u2014 this set predates the motion-capture obligation (fresh plans record it; briefs prescribe default motion doctrine only)\n"
14250
14737
  );
14251
14738
  if ("unavailable" in composition) {
@@ -14253,7 +14740,7 @@ function runRecordStatus(opts) {
14253
14740
  `);
14254
14741
  } else if (composition.openPairs.length > 0) {
14255
14742
  process.stdout.write(
14256
- `COMPOSITION ${composition.openPairs.length} unconfirmed partner pair(s): ${composition.openPairs.map((p) => `${p.displayName} [pair-key ${p.pairKey}] (${p.instances} instance(s))`).join(", ")} \u2014 this set's recording references other recorded components. Before generating the HOST, record/generate the partner and have a human decide the pairing: \`${tendrilCommand(`compose --set ${path39.resolve(opts.setDir)}`)}\` lists it; confirmation is human-only.
14743
+ `COMPOSITION ${composition.openPairs.length} unconfirmed partner pair(s): ${composition.openPairs.map((p) => `${p.displayName} [pair-key ${p.pairKey}] (${p.instances} instance(s))`).join(", ")} \u2014 this set's recording references other recorded components. Before generating the HOST, record/generate the partner and have a human decide the pairing: \`${tendrilCommand(`compose --set ${path41.resolve(opts.setDir)}`)}\` lists it; confirmation is human-only.
14257
14744
  `
14258
14745
  );
14259
14746
  } else if (composition.confirmed > 0) {
@@ -14293,7 +14780,7 @@ function narrowedRoles(derived, override) {
14293
14780
  function rolesFromFile(opts, file, derived) {
14294
14781
  let json;
14295
14782
  try {
14296
- json = JSON.parse(readFileSync26(path39.resolve(file), "utf8"));
14783
+ json = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
14297
14784
  } catch (err) {
14298
14785
  fail(opts, ExitCode.InputValidation, {
14299
14786
  error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -14331,11 +14818,11 @@ function rolesFromFile(opts, file, derived) {
14331
14818
  };
14332
14819
  }
14333
14820
  function runRecordFinish(opts) {
14334
- if (!existsSync31(path39.join(opts.setDir, "recording-set.json"))) {
14821
+ if (!existsSync32(path41.join(opts.setDir, "recording-set.json"))) {
14335
14822
  fail(opts, ExitCode.InputValidation, {
14336
14823
  error: `no recording-set.json in ${opts.setDir}`,
14337
14824
  code: "no-recording-set",
14338
- remediation: `Run \`${tendrilCommand(`record plan --set ${path39.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
14825
+ remediation: `Run \`${tendrilCommand(`record plan --set ${path41.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
14339
14826
  });
14340
14827
  }
14341
14828
  const { manifest, raw } = readManifestFile(opts.setDir);
@@ -14363,17 +14850,17 @@ function runRecordFinish(opts) {
14363
14850
  fail(opts, ExitCode.ConfirmationRequired, {
14364
14851
  error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
14365
14852
  code: "roles-confirmation-not-interactive",
14366
- remediation: `A human runs \`${tendrilCommand(`record finish --set ${path39.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
14853
+ remediation: `A human runs \`${tendrilCommand(`record finish --set ${path41.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
14367
14854
  });
14368
14855
  }
14369
14856
  const merged = { ...raw, roles };
14370
- const { issues } = validateRecordingSet(merged, (rel) => existsSync31(path39.join(opts.setDir, rel)));
14857
+ const { issues } = validateRecordingSet(merged, (rel) => existsSync32(path41.join(opts.setDir, rel)));
14371
14858
  const errors = issues.filter((i) => i.severity === "error");
14372
14859
  if (errors.length > 0) {
14373
14860
  fail(opts, ExitCode.InputValidation, {
14374
14861
  error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
14375
14862
  code: "recording-set-invalid",
14376
- remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path39.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
14863
+ remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path41.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
14377
14864
  });
14378
14865
  }
14379
14866
  for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
@@ -14423,9 +14910,9 @@ var init_record = __esm({
14423
14910
  });
14424
14911
 
14425
14912
  // packages/cli/src/font-guidance.ts
14426
- import path40 from "node:path";
14913
+ import path42 from "node:path";
14427
14914
  function fontsUnprovenRemediation(setDir) {
14428
- const set = setDir === void 0 ? void 0 : path40.resolve(setDir);
14915
+ const set = setDir === void 0 ? void 0 : path42.resolve(setDir);
14429
14916
  if (set !== void 0) {
14430
14917
  try {
14431
14918
  const needs = recordedFontNeeds(set);
@@ -14500,8 +14987,8 @@ __export(fonts_exports, {
14500
14987
  runFontsResolveSet: () => runFontsResolveSet,
14501
14988
  runFontsStatus: () => runFontsStatus
14502
14989
  });
14503
- import { existsSync as existsSync32, readFileSync as readFileSync27 } from "node:fs";
14504
- import path41 from "node:path";
14990
+ import { existsSync as existsSync33, readFileSync as readFileSync30 } from "node:fs";
14991
+ import path43 from "node:path";
14505
14992
  async function runFontsResolve(opts) {
14506
14993
  const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
14507
14994
  const queryRefusal = systemFaceRefusal(opts.family.trim());
@@ -14522,7 +15009,7 @@ async function runFontsResolve(opts) {
14522
15009
  }
14523
15010
  }
14524
15011
  async function runFontsResolveSet(opts) {
14525
- const setDir = path41.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
15012
+ const setDir = path43.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
14526
15013
  let needs = [];
14527
15014
  try {
14528
15015
  needs = recordedFontNeeds(setDir);
@@ -14573,7 +15060,10 @@ async function runFontsResolveSet(opts) {
14573
15060
  }
14574
15061
  }
14575
15062
  const refusals = [...new Set(failures.map((f) => f.family))].map((family) => ({ family, reason: systemFaceRefusal(family.trim()) })).filter((r) => r.reason !== void 0);
14576
- emitData(opts, { set: setDir, needs, cached: cached2, resolved, relicensed, unlicensed, failures, ...refusals.length > 0 ? { refusals } : {} }, () => {
15063
+ const withAFace = new Set([...cached2, ...resolved, ...relicensed].map((f) => f.family.toLowerCase()));
15064
+ const familyMisses = failures.filter((f) => !withAFace.has(f.family.toLowerCase()));
15065
+ const weightMisses = failures.filter((f) => withAFace.has(f.family.toLowerCase()));
15066
+ emitData(opts, { set: setDir, needs, cached: cached2, resolved, relicensed, unlicensed, failures, familyMisses, weightMisses, ...refusals.length > 0 ? { refusals } : {} }, () => {
14577
15067
  process.stdout.write(`set declares: ${needs.map((n) => `${n.family} (${n.weights.join(", ")})`).join(" \xB7 ")}
14578
15068
  `);
14579
15069
  for (const f of cached2) process.stdout.write(`cached ${f.family} ${f.weight}
@@ -14599,22 +15089,31 @@ async function runFontsResolveSet(opts) {
14599
15089
  if (byteDrift.length > 0) {
14600
15090
  warn(opts, `${byteDrift.map((f) => `${f.family} ${f.weight}`).join(", ")}: the foundry now serves different bytes than the cache held \u2014 the faces were re-fetched to record their licence, so re-pin any font lock deliberately`);
14601
15091
  }
14602
- if (failures.length > 0) {
14603
- warn(opts, `${failures.length} face(s) unresolved \u2014 verification will refuse to score under substitution`);
15092
+ if (weightMisses.length > 0) {
15093
+ const byFamily = [...new Set(weightMisses.map((f) => f.family))].map(
15094
+ (fam) => `"${fam}" ${weightMisses.filter((f) => f.family === fam).map((f) => f.weight).join(", ")}`
15095
+ );
15096
+ warn(
15097
+ opts,
15098
+ `weight instance(s) unavailable: ${byFamily.join("; ")} \u2014 the FAMILY is provisioned, so the mount renders these at the nearest cached weight. Verify reports that as an advisory and never gates on it, so this run is not a failure. If the exact instance matters, register a file you licence: ${tendrilCommand('fonts add "<Family>" <weight> <file>')}.`
15099
+ );
15100
+ }
15101
+ if (familyMisses.length > 0) {
15102
+ warn(opts, `${familyMisses.length} face(s) unresolved with NO cached face for the family \u2014 verification will refuse to certify under substitution`);
14604
15103
  process.exitCode = ExitCode.FontsUnproven;
14605
15104
  }
14606
15105
  }
14607
15106
  function runFontsStatus(opts) {
14608
- const manifestPath2 = path41.join(opts.cacheDir, "manifest.json");
14609
- if (!existsSync32(manifestPath2)) {
15107
+ const manifestPath2 = path43.join(opts.cacheDir, "manifest.json");
15108
+ if (!existsSync33(manifestPath2)) {
14610
15109
  fail(opts, ExitCode.FontsUnproven, {
14611
15110
  error: `no font cache at ${opts.cacheDir}`,
14612
15111
  code: "fonts-unresolved",
14613
15112
  remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
14614
15113
  });
14615
15114
  }
14616
- const faces = JSON.parse(readFileSync27(manifestPath2, "utf8"));
14617
- const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path41.resolve(opts.lock), opts.cacheDir) : null;
15115
+ const faces = JSON.parse(readFileSync30(manifestPath2, "utf8"));
15116
+ const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path43.resolve(opts.lock), opts.cacheDir) : null;
14618
15117
  emitData(opts, { faces, lockVerdicts }, () => {
14619
15118
  for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
14620
15119
  `);
@@ -14658,13 +15157,13 @@ function familyMismatch(family, declared) {
14658
15157
  }
14659
15158
  function runFontsAdd(opts) {
14660
15159
  if (opts.set !== void 0) {
14661
- const declared = taskFontFamilies(path41.resolve(opts.set)) ?? [];
15160
+ const declared = taskFontFamilies(path43.resolve(opts.set)) ?? [];
14662
15161
  const mismatch = familyMismatch(opts.family, declared);
14663
15162
  if (mismatch !== void 0) {
14664
15163
  fail(opts, ExitCode.InputValidation, {
14665
15164
  error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. Adding it under this name would cache a face the mount never matches, and scoring would keep refusing for the family that is still missing.`,
14666
15165
  code: "font-family-not-declared",
14667
- remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path41.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
15166
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path43.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
14668
15167
  });
14669
15168
  }
14670
15169
  } else {
@@ -14723,13 +15222,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
14723
15222
  }
14724
15223
  function runFontsAddSystem(opts) {
14725
15224
  if (opts.set !== void 0) {
14726
- const declared = taskFontFamilies(path41.resolve(opts.set)) ?? [];
15225
+ const declared = taskFontFamilies(path43.resolve(opts.set)) ?? [];
14727
15226
  const mismatch = familyMismatch(opts.family, declared);
14728
15227
  if (mismatch !== void 0) {
14729
15228
  fail(opts, ExitCode.InputValidation, {
14730
15229
  error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. A face cached under a name the mount never matches leaves scoring refusing for the family that is still missing.`,
14731
15230
  code: "font-family-not-declared",
14732
- remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path41.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
15231
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path43.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
14733
15232
  });
14734
15233
  }
14735
15234
  } else {
@@ -14779,12 +15278,12 @@ var init_fonts = __esm({
14779
15278
  });
14780
15279
 
14781
15280
  // packages/cli/src/profile-input.ts
14782
- import { existsSync as existsSync33, readFileSync as readFileSync28 } from "node:fs";
14783
- import path42 from "node:path";
15281
+ import { existsSync as existsSync34, readFileSync as readFileSync31 } from "node:fs";
15282
+ import path44 from "node:path";
14784
15283
  function loadCodebaseProfile(flags, profilePath) {
14785
15284
  if (profilePath === void 0) return null;
14786
- const abs = path42.resolve(profilePath);
14787
- if (!existsSync33(abs)) {
15285
+ const abs = path44.resolve(profilePath);
15286
+ if (!existsSync34(abs)) {
14788
15287
  fail(flags, ExitCode.InputValidation, {
14789
15288
  error: `no profile at ${abs}`,
14790
15289
  code: "profile_missing",
@@ -14792,7 +15291,7 @@ function loadCodebaseProfile(flags, profilePath) {
14792
15291
  });
14793
15292
  }
14794
15293
  try {
14795
- return readCodebaseProfile(readFileSync28(abs, "utf8"));
15294
+ return readCodebaseProfile(readFileSync31(abs, "utf8"));
14796
15295
  } catch (error) {
14797
15296
  fail(flags, ExitCode.InputValidation, {
14798
15297
  error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
@@ -14834,8 +15333,8 @@ __export(verify_exports, {
14834
15333
  runVerify: () => runVerify,
14835
15334
  verdictCaveatsFor: () => verdictCaveatsFor
14836
15335
  });
14837
- import { existsSync as existsSync34, readFileSync as readFileSync29 } from "node:fs";
14838
- import path43 from "node:path";
15336
+ import { existsSync as existsSync35, readFileSync as readFileSync32, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
15337
+ import path45 from "node:path";
14839
15338
  function interactionCoverage(behaviors) {
14840
15339
  const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
14841
15340
  const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
@@ -14912,7 +15411,6 @@ function operabilityLine(state) {
14912
15411
  return void 0;
14913
15412
  }
14914
15413
  function foldConfigStatus(s, failDemotions, substitutedFamilies) {
14915
- const { exact: _exact, ...reported } = s;
14916
15414
  let status = tierOf(s, BARS2.cert);
14917
15415
  const certDemote = [];
14918
15416
  let absentInkDemoted = false;
@@ -14935,7 +15433,7 @@ function foldConfigStatus(s, failDemotions, substitutedFamilies) {
14935
15433
  status = "pass";
14936
15434
  certDemote.push(`substituted fonts: cache lacks ${substitutedFamilies.map((f) => `"${f}"`).join(", ")} \u2014 certification never measures a substitute face`);
14937
15435
  }
14938
- const base = certDemote.length > 0 ? { ...reported, status, demotedBy: certDemote } : { ...reported, status };
15436
+ const base = certDemote.length > 0 ? { ...s, status, demotedBy: certDemote } : { ...s, status };
14939
15437
  return {
14940
15438
  row: failDemotions === void 0 ? base : { ...base, status: "fail", demotedBy: [...certDemote, ...failDemotions] },
14941
15439
  absentInkDemoted
@@ -15077,10 +15575,26 @@ function compositionReport(input) {
15077
15575
  function eyeCheck(bundleDir) {
15078
15576
  return {
15079
15577
  command: tendrilCommand(`inspect "${bundleDir}"`),
15080
- sheetPath: path43.join(bundleDir, "verify-evidence", "inspect.html"),
15578
+ sheetPath: path45.join(bundleDir, "verify-evidence", "inspect.html"),
15081
15579
  note: "magnified recorded-vs-rendered crops of every small node; scores cannot see shape. The command WRITES/refreshes the sheet at sheetPath \u2014 re-run it after every verify; an existing sheet may show stale crops."
15082
15580
  };
15083
15581
  }
15582
+ function evidenceArtifacts(evidenceDir, reps) {
15583
+ const named = (name) => existsSync35(path45.join(evidenceDir, name)) ? name : null;
15584
+ return {
15585
+ legend: named("diff-legend.txt"),
15586
+ configs: reps.map((rep) => {
15587
+ const absentInk = [];
15588
+ for (let i = 0; ; i += 1) {
15589
+ const ref = named(`${rep}-absent-${i}-ref.png`);
15590
+ const render = named(`${rep}-absent-${i}-render.png`);
15591
+ if (ref === null || render === null) break;
15592
+ absentInk.push({ ref, render });
15593
+ }
15594
+ return { rep, render: named(`${rep}-render.png`), ref: named(`${rep}-ref.png`), diff: named(`${rep}-diff.png`), absentInk };
15595
+ })
15596
+ };
15597
+ }
15084
15598
  function failureTally(t) {
15085
15599
  return `${t.configs.length} config(s), ${t.behaviors.length} behavior(s), ${t.structural.length + t.crops.length} composition check(s), ${t.occlusion.length} occlusion check(s) below the ${t.bar} bar${t.evidenceUnverified ? "; PLUS the interaction-evidence instrument gate failed (operability could not be verified \u2014 see FAIL interaction-evidence above)" : ""} \u2014 bundle written, verdict honest (Q4)`;
15086
15600
  }
@@ -15114,7 +15628,7 @@ function taskFromManifest(opts, manifest, setDir) {
15114
15628
  const adapterSlugs = Object.keys(manifest.propAdapter);
15115
15629
  const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
15116
15630
  const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
15117
- const registry = Object.values(TASKS).find((t) => path43.resolve(t.set) === path43.resolve(setDir));
15631
+ const registry = Object.values(TASKS).find((t) => path45.resolve(t.set) === path45.resolve(setDir));
15118
15632
  const authored = (() => {
15119
15633
  if (registry !== void 0) return void 0;
15120
15634
  try {
@@ -15174,19 +15688,19 @@ function verdictCaveatsFor(input) {
15174
15688
  async function runVerify(opts) {
15175
15689
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
15176
15690
  let recordingSetDrift;
15177
- const setOverride = opts.set !== void 0 ? path43.resolve(callerCwd, opts.set) : void 0;
15178
- opts = { ...opts, bundleDir: path43.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
15179
- if (!existsSync34(opts.bundleDir)) {
15691
+ const setOverride = opts.set !== void 0 ? path45.resolve(callerCwd, opts.set) : void 0;
15692
+ opts = { ...opts, bundleDir: path45.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
15693
+ if (!existsSync35(opts.bundleDir)) {
15180
15694
  fail(opts, ExitCode.InputValidation, {
15181
15695
  error: `bundle directory not found: ${opts.bundleDir}`,
15182
15696
  code: "bundle-missing",
15183
15697
  remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
15184
15698
  });
15185
15699
  }
15186
- const manifestPath2 = path43.join(opts.bundleDir, "component.json");
15700
+ const manifestPath2 = path45.join(opts.bundleDir, "component.json");
15187
15701
  let manifest;
15188
- if (existsSync34(manifestPath2)) {
15189
- const { manifest: parsed, issues } = readBundleManifest(readFileSync29(manifestPath2, "utf8"));
15702
+ if (existsSync35(manifestPath2)) {
15703
+ const { manifest: parsed, issues } = readBundleManifest(readFileSync32(manifestPath2, "utf8"));
15190
15704
  if (issues.length > 0) {
15191
15705
  fail(opts, ExitCode.InputValidation, {
15192
15706
  error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15217,21 +15731,21 @@ async function runVerify(opts) {
15217
15731
  task = registry;
15218
15732
  } else if (manifest !== void 0) {
15219
15733
  const resolveSetDir = (p) => {
15220
- if (path43.isAbsolute(p)) return p;
15221
- const fromRepo = path43.resolve(REPO_ROOT, p);
15222
- if (existsSync34(fromRepo)) return fromRepo;
15223
- return path43.resolve(callerCwd, p);
15734
+ if (path45.isAbsolute(p)) return p;
15735
+ const fromRepo = path45.resolve(REPO_ROOT, p);
15736
+ if (existsSync35(fromRepo)) return fromRepo;
15737
+ return path45.resolve(callerCwd, p);
15224
15738
  };
15225
15739
  const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
15226
- if (!existsSync34(path43.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path43.resolve(t.set) === path43.resolve(setDir))) {
15740
+ if (!existsSync35(path45.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path45.resolve(t.set) === path45.resolve(setDir))) {
15227
15741
  fail(opts, ExitCode.RecordingIncomplete, {
15228
15742
  error: `recording set not found or unmanifested: ${setDir}`,
15229
15743
  code: "recording-set-missing",
15230
15744
  remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
15231
15745
  });
15232
15746
  }
15233
- const registry = Object.values(TASKS).find((t) => path43.resolve(t.set) === path43.resolve(setDir));
15234
- if (registry !== void 0 && !existsSync34(path43.join(setDir, "recording-set.json"))) {
15747
+ const registry = Object.values(TASKS).find((t) => path45.resolve(t.set) === path45.resolve(setDir));
15748
+ if (registry !== void 0 && !existsSync35(path45.join(setDir, "recording-set.json"))) {
15235
15749
  const recordedSlugs = registry.configs.map((c) => c.rep);
15236
15750
  const adapterSlugs = Object.keys(manifest.propAdapter);
15237
15751
  unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
@@ -15263,9 +15777,9 @@ async function runVerify(opts) {
15263
15777
  recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
15264
15778
  }
15265
15779
  for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
15266
- const p = path43.join(opts.bundleDir, name);
15267
- if (!existsSync34(p)) continue;
15268
- const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync29(p)));
15780
+ const p = path45.join(opts.bundleDir, name);
15781
+ if (!existsSync35(p)) continue;
15782
+ const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync32(p)));
15269
15783
  if (issues.length > 0) {
15270
15784
  fail(opts, ExitCode.InputValidation, {
15271
15785
  error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15303,7 +15817,7 @@ async function runVerify(opts) {
15303
15817
  warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
15304
15818
  }
15305
15819
  const missing = task.configs.filter(
15306
- (c) => !existsSync34(path43.join(task.set, c.rep, "get_screenshot.json")) || !existsSync34(path43.join(task.set, c.rep, "get_metadata.json"))
15820
+ (c) => !existsSync35(path45.join(task.set, c.rep, "get_screenshot.json")) || !existsSync35(path45.join(task.set, c.rep, "get_metadata.json"))
15307
15821
  );
15308
15822
  if (missing.length > 0) {
15309
15823
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -15313,7 +15827,8 @@ async function runVerify(opts) {
15313
15827
  });
15314
15828
  }
15315
15829
  const bar = BARS2[opts.bar];
15316
- const evidenceDir = path43.join(opts.bundleDir, "verify-evidence");
15830
+ const evidenceDir = path45.join(opts.bundleDir, "verify-evidence");
15831
+ rmSync5(evidenceDir, { recursive: true, force: true });
15317
15832
  const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
15318
15833
  const quality = await checkBundleQuality(
15319
15834
  opts.bundleDir,
@@ -15330,7 +15845,7 @@ async function runVerify(opts) {
15330
15845
  // ASKED, never "follows every convention".
15331
15846
  loadCodebaseProfile(opts, opts.profile) ?? void 0
15332
15847
  );
15333
- const bundleCss = ["tokens.css", "styles.css"].map((f) => path43.join(opts.bundleDir, f)).filter((f) => existsSync34(f)).map((f) => readFileSync29(f, "utf8")).join("\n");
15848
+ const bundleCss = ["tokens.css", "styles.css"].map((f) => path45.join(opts.bundleDir, f)).filter((f) => existsSync35(f)).map((f) => readFileSync32(f, "utf8")).join("\n");
15334
15849
  const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
15335
15850
  const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs);
15336
15851
  const framing = checkAdapterFraming(
@@ -15346,10 +15861,10 @@ async function runVerify(opts) {
15346
15861
  warn(opts, `compositions extension REJECTED (${crossComposition.malformed}) \u2014 the cross-bundle backstop did NOT run over it; repair the manifest entry and re-verify. This is an instrument failure, not a clean bill.`);
15347
15862
  }
15348
15863
  const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
15349
- const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path43.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
15864
+ const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path45.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
15350
15865
  const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
15351
15866
  const renderExpectations = verifyPins.pins.map((pin) => ({
15352
- modulePath: path43.join(composedModuleDir(pin.partnerName), pin.entryComponent),
15867
+ modulePath: path45.join(composedModuleDir(pin.partnerName), pin.entryComponent),
15353
15868
  component: pin.entryComponent,
15354
15869
  stampName: `${pin.pairKey}:${pin.entryComponent}`,
15355
15870
  hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
@@ -15407,8 +15922,10 @@ async function runVerify(opts) {
15407
15922
  }
15408
15923
  })();
15409
15924
  const compositionBlock = compositionReport({ availability, structural, crops, regions: regionsOut, composedPairs: composedPairs.size });
15925
+ const scoredFiles = digestScoredFiles(opts.bundleDir, manifest !== void 0 ? manifest.entry : task.entry);
15410
15926
  const report = {
15411
15927
  bundle: opts.bundleDir,
15928
+ scoredFiles,
15412
15929
  ...opts.task !== void 0 ? { task: opts.task } : {},
15413
15930
  ...manifest !== void 0 ? { bundleManifest: { name: manifest.name, bundleVersion: manifest.bundleVersion, recordingSetHash: manifest.provenance.recordingSet.hash } } : {},
15414
15931
  targetBar: opts.bar,
@@ -15473,7 +15990,7 @@ async function runVerify(opts) {
15473
15990
  } : {},
15474
15991
  configs: statuses,
15475
15992
  behaviors,
15476
- evidence: { dir: evidenceDir, files: "per config: <rep>-render.png, <rep>-ref.png, <rep>-diff.png" },
15993
+ evidence: { dir: evidenceDir, ...evidenceArtifacts(evidenceDir, statuses.map((s) => s.rep)) },
15477
15994
  composition: compositionBlock,
15478
15995
  verdict: ok ? "verified" : "verification-failed",
15479
15996
  // Run-23 R5: the one-word verdict printed beside "coverage
@@ -15634,7 +16151,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
15634
16151
  }
15635
16152
  process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
15636
16153
  `);
15637
- const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path43.join(opts.bundleDir, f)).filter((f) => existsSync34(f)).map((f) => readFileSync29(f, "utf8")).join("\n")));
16154
+ const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path45.join(opts.bundleDir, f)).filter((f) => existsSync35(f)).map((f) => readFileSync32(f, "utf8")).join("\n")));
15638
16155
  if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
15639
16156
  process.stdout.write(`fonts: scored with Tendril-cache faces \u2014 a consuming app must provision the same families (the bundle ships fonts.css when faces are shippable; sha-pinned list in component.json requiredFonts)
15640
16157
  `);
@@ -15686,8 +16203,27 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
15686
16203
  );
15687
16204
  process.exitCode = ExitCode.VerificationFailed;
15688
16205
  }
16206
+ persistReport(opts, report, evidenceDir);
15689
16207
  }
15690
- var BARS2, NO_INTERACTIVE_POSES, NO_ROLE_MANIFEST, ROLES_NOT_RESOLVED, UNSTAMPED_ROLES, COMPOSE_ON_GENERATE_ARMED, NO_OVERLAY_DECLARED;
16208
+ function persistReport(opts, report, evidenceDir) {
16209
+ if (!existsSync35(evidenceDir)) return;
16210
+ const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
16211
+ const withExit = {
16212
+ ...report,
16213
+ rulerExit,
16214
+ ...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
16215
+ };
16216
+ try {
16217
+ writeFileSync14(
16218
+ path45.join(evidenceDir, VERIFY_REPORT_FILENAME),
16219
+ `${JSON.stringify(verifyReportForTransport(withExit, path45.basename(opts.bundleDir)), null, 2)}
16220
+ `
16221
+ );
16222
+ } catch (e) {
16223
+ warn(opts, `could not write ${VERIFY_REPORT_FILENAME} (${e instanceof Error ? e.message : String(e)}) \u2014 the verdict above stands, but this run left no persisted report beside its evidence.`);
16224
+ }
16225
+ }
16226
+ var BARS2, NO_INTERACTIVE_POSES, NO_ROLE_MANIFEST, ROLES_NOT_RESOLVED, UNSTAMPED_ROLES, COMPOSE_ON_GENERATE_ARMED, NO_OVERLAY_DECLARED, EXIT_REFUSALS;
15691
16227
  var init_verify = __esm({
15692
16228
  "packages/cli/src/commands/verify.ts"() {
15693
16229
  "use strict";
@@ -15717,6 +16253,10 @@ var init_verify = __esm({
15717
16253
  UNSTAMPED_ROLES = "unstamped";
15718
16254
  COMPOSE_ON_GENERATE_ARMED = false;
15719
16255
  NO_OVERLAY_DECLARED = "no overlay declared";
16256
+ EXIT_REFUSALS = {
16257
+ [ExitCode.VerificationFailed]: "the ruler refused this run: one or more checks fell below the requested bar",
16258
+ [ExitCode.FontsUnproven]: "the ruler refused this run: required font faces were not provisioned, so scores measure a substitute"
16259
+ };
15720
16260
  }
15721
16261
  });
15722
16262
 
@@ -15726,11 +16266,11 @@ __export(engine_exports, {
15726
16266
  runEngineBrief: () => runEngineBrief,
15727
16267
  runEngineScore: () => runEngineScore
15728
16268
  });
15729
- import { appendFileSync, existsSync as existsSync35, mkdirSync as mkdirSync9, readFileSync as readFileSync30, writeFileSync as writeFileSync13 } from "node:fs";
15730
- import path44 from "node:path";
16269
+ import { appendFileSync, existsSync as existsSync36, mkdirSync as mkdirSync10, readFileSync as readFileSync33, writeFileSync as writeFileSync15 } from "node:fs";
16270
+ import path46 from "node:path";
15731
16271
  function resolveEngineTask(opts, callerCwd) {
15732
- const asPath = path44.resolve(callerCwd, opts.taskOrSet);
15733
- const isSet = existsSync35(path44.join(asPath, "recording-set.json"));
16272
+ const asPath = path46.resolve(callerCwd, opts.taskOrSet);
16273
+ const isSet = existsSync36(path46.join(asPath, "recording-set.json"));
15734
16274
  const registry = TASKS[opts.taskOrSet];
15735
16275
  if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
15736
16276
  if (isSet) {
@@ -15739,7 +16279,7 @@ function resolveEngineTask(opts, callerCwd) {
15739
16279
  for (const d of authored.disclosures) warn(opts, d);
15740
16280
  return {
15741
16281
  task: authored.task,
15742
- name: path44.basename(asPath),
16282
+ name: path46.basename(asPath),
15743
16283
  ref: asPath,
15744
16284
  disclosures: authored.disclosures,
15745
16285
  interactionEvidence: authored.api.interactionEvidence,
@@ -15767,13 +16307,13 @@ function runEngineBrief(opts) {
15767
16307
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
15768
16308
  const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
15769
16309
  const bar = BARS3[opts.bar];
15770
- if (existsSync35(path44.join(task.set, "recording-set.json"))) {
16310
+ if (existsSync36(path46.join(task.set, "recording-set.json"))) {
15771
16311
  try {
15772
- const { open } = compositionPairsFor(path44.resolve(task.set), [opts.library !== void 0 ? path44.resolve(callerCwd, opts.library) : callerCwd]);
16312
+ const { open } = compositionPairsFor(path46.resolve(task.set), [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
15773
16313
  if (open.length > 0) {
15774
16314
  const componentNames = [...new Set(open.map((p) => p.displayName))];
15775
16315
  disclosures.push(
15776
- `COMPOSITION PAIRS UNDECIDED: this recording references ${componentNames.length} other recorded component(s) (${open.map((p) => `${p.displayName} [pair-key ${p.key}]: ${p.instances.length} instance(s)`).join("; ")}) and no human has decided the pairing. This brief treats the component as SELF-CONTAINED \u2014 you will re-implement the partner's pixels locally. If composition is wanted: generate the partner bundle first, have a human run \`${tendrilCommand(`compose --set ${path44.resolve(task.set)}`)}\` in their terminal, and re-emit this brief. Proceeding as-is is valid; it just is not composition.`
16316
+ `COMPOSITION PAIRS UNDECIDED: this recording references ${componentNames.length} other recorded component(s) (${open.map((p) => `${p.displayName} [pair-key ${p.key}]: ${p.instances.length} instance(s)`).join("; ")}) and no human has decided the pairing. This brief treats the component as SELF-CONTAINED \u2014 you will re-implement the partner's pixels locally. If composition is wanted: generate the partner bundle first, have a human run \`${tendrilCommand(`compose --set ${path46.resolve(task.set)}`)}\` in their terminal, and re-emit this brief. Proceeding as-is is valid; it just is not composition.`
15777
16317
  );
15778
16318
  }
15779
16319
  } catch (err) {
@@ -15790,9 +16330,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
15790
16330
  const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
15791
16331
  const segments = buildSegments(task, "files");
15792
16332
  let notRecorded;
15793
- const manifestPath2 = path44.join(task.set, "recording-set.json");
15794
- if (existsSync35(manifestPath2)) {
15795
- notRecorded = JSON.parse(readFileSync30(manifestPath2, "utf8")).notRecorded;
16333
+ const manifestPath2 = path46.join(task.set, "recording-set.json");
16334
+ if (existsSync36(manifestPath2)) {
16335
+ notRecorded = JSON.parse(readFileSync33(manifestPath2, "utf8")).notRecorded;
15796
16336
  }
15797
16337
  const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
15798
16338
 
@@ -15800,7 +16340,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
15800
16340
  DO NOT INVENT PIXELS FOR THESE POSES. Implement them ONLY as the composition of recorded per-axis truth (the custom-property composition rule: one paint axis sets variables, the other consumes) and list every such pose in your report as UNRECORDED-COMPOSED. Recording them is the real fix: re-plan the set (full matrix is the default) and record the missing poses.
15801
16341
  ${notRecorded}` : "";
15802
16342
  let fontProvisioning;
15803
- if (existsSync35(manifestPath2)) {
16343
+ if (existsSync36(manifestPath2)) {
15804
16344
  const missingFams = unprovisionedFamilies(task.set);
15805
16345
  const unprovided = unprovisionedFaces(task.set);
15806
16346
  const weightOnly = missingFams.length === 0;
@@ -15822,7 +16362,7 @@ ${notRecorded}` : "";
15822
16362
  };
15823
16363
  }
15824
16364
  }
15825
- const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path44.resolve(callerCwd, opts.library) : callerCwd]);
16365
+ const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
15826
16366
  const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
15827
16367
  const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
15828
16368
 
@@ -15858,10 +16398,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
15858
16398
 
15859
16399
  === TASK PAYLOAD (recorded truth, verbatim) ===
15860
16400
  ${segments}`;
15861
- const payloadFile = path44.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
15862
- const candidateDirSuggestion = path44.resolve(callerCwd, `tendril-out/${name}-candidate`);
15863
- mkdirSync9(path44.dirname(payloadFile), { recursive: true });
15864
- writeFileSync13(payloadFile, payload);
16401
+ const payloadFile = path46.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
16402
+ const candidateDirSuggestion = path46.resolve(callerCwd, `tendril-out/${name}-candidate`);
16403
+ mkdirSync10(path46.dirname(payloadFile), { recursive: true });
16404
+ writeFileSync15(payloadFile, payload);
15865
16405
  emitData(
15866
16406
  opts,
15867
16407
  {
@@ -15907,7 +16447,7 @@ ${segments}`;
15907
16447
  // command must search the same bundle roots the pins came
15908
16448
  // from, or the oracle and the brief describe different worlds.
15909
16449
  `Run \`${tendrilCommand(
15910
- `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path44.resolve(callerCwd, opts.library))}` : ""} --json`
16450
+ `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path46.resolve(callerCwd, opts.library))}` : ""} --json`
15911
16451
  )}\` \u2014 the CLI is the sole judge, and it refuses to score an undeclared model. If you wrote the bundle elsewhere, pass that directory instead.`,
15912
16452
  "Apply the returned feedback and re-score. Stop when all checks pass. Two consecutive non-improving scores mean INSPECT the verify-evidence diffs before deciding \u2014 stop only when inspection yields no fix hypothesis (a measured run was byte-identical twice, then went 27/27 after reading the diffs).",
15913
16453
  "Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
@@ -15922,8 +16462,8 @@ ${segments}`;
15922
16462
  );
15923
16463
  }
15924
16464
  function appendScoreHistory(candidateDir, entry) {
15925
- const file = path44.join(candidateDir, "score-history.jsonl");
15926
- const starts = existsSync35(file) ? readFileSync30(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
16465
+ const file = path46.join(candidateDir, "score-history.jsonl");
16466
+ const starts = existsSync36(file) ? readFileSync33(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
15927
16467
  const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
15928
16468
  appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
15929
16469
  `);
@@ -15931,9 +16471,9 @@ function appendScoreHistory(candidateDir, entry) {
15931
16471
  async function runEngineScore(opts) {
15932
16472
  requireEntitlement(opts);
15933
16473
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
15934
- const candidateDir = path44.resolve(callerCwd, opts.candidateDir);
16474
+ const candidateDir = path46.resolve(callerCwd, opts.candidateDir);
15935
16475
  const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
15936
- if (!existsSync35(candidateDir)) {
16476
+ if (!existsSync36(candidateDir)) {
15937
16477
  fail(opts, ExitCode.InputValidation, {
15938
16478
  error: `candidate directory not found: ${candidateDir}`,
15939
16479
  code: "candidate-missing",
@@ -15958,10 +16498,10 @@ async function runEngineScore(opts) {
15958
16498
  for (const g of missingWeights(task.set)) {
15959
16499
  warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
15960
16500
  }
15961
- if (opts.rebind !== true && existsSync35(path44.join(candidateDir, "component.json"))) {
16501
+ if (opts.rebind !== true && existsSync36(path46.join(candidateDir, "component.json"))) {
15962
16502
  const prior = (() => {
15963
16503
  try {
15964
- const read = readBundleManifest(readFileSync30(path44.join(candidateDir, "component.json"), "utf8"));
16504
+ const read = readBundleManifest(readFileSync33(path46.join(candidateDir, "component.json"), "utf8"));
15965
16505
  return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
15966
16506
  } catch {
15967
16507
  return { unreadable: true };
@@ -15983,12 +16523,12 @@ async function runEngineScore(opts) {
15983
16523
  }
15984
16524
  }
15985
16525
  const bar = BARS3[opts.bar];
15986
- const evidenceDir = path44.join(candidateDir, "verify-evidence");
16526
+ const evidenceDir = path46.join(candidateDir, "verify-evidence");
15987
16527
  const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
15988
16528
  const parity = await checkHoverParity(task, candidateDir, task.configs);
15989
16529
  const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
15990
16530
  const occlusionFailures = occlusionRows.filter((o) => !o.pass);
15991
- const scorePins = composedPins(task.set, [opts.library !== void 0 ? path44.resolve(callerCwd, opts.library) : callerCwd]);
16531
+ const scorePins = composedPins(task.set, [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
15992
16532
  const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
15993
16533
  const behaviors = [...await checkBehaviors(task, candidateDir), ...parity, ...composition];
15994
16534
  const parityCoverage = parity.length > 0 ? `${parity.filter((p) => p.pass).length}/${parity.length} hover-forced configs` : "not applicable (no hover-forced configs in this set)";
@@ -16206,11 +16746,11 @@ var codeconnect_exports = {};
16206
16746
  __export(codeconnect_exports, {
16207
16747
  runCodeConnect: () => runCodeConnect
16208
16748
  });
16209
- import { existsSync as existsSync36, readFileSync as readFileSync31, writeFileSync as writeFileSync14 } from "node:fs";
16210
- import path45 from "node:path";
16749
+ import { existsSync as existsSync37, readFileSync as readFileSync34, writeFileSync as writeFileSync16 } from "node:fs";
16750
+ import path47 from "node:path";
16211
16751
  function runCodeConnect(opts) {
16212
16752
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16213
- const bundleDir = path45.resolve(callerCwd, opts.bundleDir);
16753
+ const bundleDir = path47.resolve(callerCwd, opts.bundleDir);
16214
16754
  let url;
16215
16755
  try {
16216
16756
  url = new URL(opts.figmaUrl);
@@ -16226,7 +16766,7 @@ function runCodeConnect(opts) {
16226
16766
  }
16227
16767
  let manifest;
16228
16768
  try {
16229
- const read = readBundleManifest(readFileSync31(path45.join(bundleDir, "component.json"), "utf8"));
16769
+ const read = readBundleManifest(readFileSync34(path47.join(bundleDir, "component.json"), "utf8"));
16230
16770
  if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
16231
16771
  manifest = read.manifest;
16232
16772
  } catch (err) {
@@ -16236,8 +16776,8 @@ function runCodeConnect(opts) {
16236
16776
  remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
16237
16777
  });
16238
16778
  }
16239
- const setDir = path45.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16240
- if (!existsSync36(path45.join(setDir, "recording-set.json"))) {
16779
+ const setDir = path47.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16780
+ if (!existsSync37(path47.join(setDir, "recording-set.json"))) {
16241
16781
  fail(opts, ExitCode.InputValidation, {
16242
16782
  error: `recording set not found at ${setDir}`,
16243
16783
  code: "codeconnect-no-set",
@@ -16258,10 +16798,10 @@ function runCodeConnect(opts) {
16258
16798
  const component = api.component;
16259
16799
  const recManifest = loadManifest(setDir);
16260
16800
  const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
16261
- const meta = path45.join(setDir, r.slug, "get_metadata.json");
16262
- if (!existsSync36(meta)) return void 0;
16801
+ const meta = path47.join(setDir, r.slug, "get_metadata.json");
16802
+ if (!existsSync37(meta)) return void 0;
16263
16803
  try {
16264
- return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync31(meta, "utf8"))))?.[1];
16804
+ return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync34(meta, "utf8"))))?.[1];
16265
16805
  } catch {
16266
16806
  return void 0;
16267
16807
  }
@@ -16326,7 +16866,7 @@ function runCodeConnect(opts) {
16326
16866
  axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
16327
16867
  fragmentVars.push(varName);
16328
16868
  }
16329
- const entryRel = path45.relative(callerCwd, path45.join(bundleDir, manifest.entry));
16869
+ const entryRel = path47.relative(callerCwd, path47.join(bundleDir, manifest.entry));
16330
16870
  const trust = manifest.trustStatement.split("\n")[0] ?? "";
16331
16871
  const lines = [
16332
16872
  `// url=${opts.figmaUrl}`,
@@ -16350,8 +16890,8 @@ function runCodeConnect(opts) {
16350
16890
  `}`,
16351
16891
  ``
16352
16892
  ].join("\n");
16353
- const outFile = path45.resolve(callerCwd, opts.out ?? path45.join(bundleDir, `${component}.figma.ts`));
16354
- writeFileSync14(outFile, lines);
16893
+ const outFile = path47.resolve(callerCwd, opts.out ?? path47.join(bundleDir, `${component}.figma.ts`));
16894
+ writeFileSync16(outFile, lines);
16355
16895
  emitData(
16356
16896
  opts,
16357
16897
  {
@@ -16389,18 +16929,18 @@ var init_codeconnect = __esm({
16389
16929
  });
16390
16930
 
16391
16931
  // packages/mcp/src/server.ts
16392
- import { createHash as createHash8 } from "node:crypto";
16393
- import { existsSync as existsSync37, mkdtempSync as mkdtempSync3, readFileSync as readFileSync32, readdirSync as readdirSync14, writeFileSync as writeFileSync15 } from "node:fs";
16394
- import os7 from "node:os";
16395
- import path46 from "node:path";
16932
+ import { createHash as createHash9 } from "node:crypto";
16933
+ import { existsSync as existsSync38, mkdtempSync as mkdtempSync3, readFileSync as readFileSync35, readdirSync as readdirSync15, writeFileSync as writeFileSync17 } from "node:fs";
16934
+ import os8 from "node:os";
16935
+ import path48 from "node:path";
16396
16936
  import { fileURLToPath as fileURLToPath6 } from "node:url";
16397
16937
  import { z as z14 } from "zod";
16398
16938
  function sourceHash() {
16399
- const dir = path46.dirname(fileURLToPath6(import.meta.url));
16400
- const h = createHash8("sha256");
16401
- for (const f of readdirSync14(dir).filter((n) => n.endsWith(".ts")).sort()) {
16939
+ const dir = path48.dirname(fileURLToPath6(import.meta.url));
16940
+ const h = createHash9("sha256");
16941
+ for (const f of readdirSync15(dir).filter((n) => n.endsWith(".ts")).sort()) {
16402
16942
  h.update(f);
16403
- h.update(readFileSync32(path46.join(dir, f)));
16943
+ h.update(readFileSync35(path48.join(dir, f)));
16404
16944
  }
16405
16945
  return h.digest("hex").slice(0, 16);
16406
16946
  }
@@ -16408,10 +16948,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
16408
16948
  var init_server = __esm({
16409
16949
  "packages/mcp/src/server.ts"() {
16410
16950
  "use strict";
16411
- REPO_ROOT3 = path46.resolve(path46.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
16412
- CLI_BIN = path46.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
16413
- BUNDLED_CLI = path46.join(path46.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
16414
- CLI_SPAWN = existsSync37(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
16951
+ REPO_ROOT3 = path48.resolve(path48.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
16952
+ CLI_BIN = path48.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
16953
+ BUNDLED_CLI = path48.join(path48.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
16954
+ CLI_SPAWN = existsSync38(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
16415
16955
  str = (d) => z14.string().describe(d);
16416
16956
  optStr = (d) => z14.string().optional().describe(d);
16417
16957
  TOOLS = [
@@ -16442,13 +16982,13 @@ var init_server = __esm({
16442
16982
  const single = i["metadata"];
16443
16983
  const parts = i["metadataParts"];
16444
16984
  if (single !== void 0 || parts !== void 0) {
16445
- const tmp = path46.join(mkdtempSync3(path46.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
16985
+ const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
16446
16986
  if (single !== void 0) {
16447
- writeFileSync15(tmp, single);
16987
+ writeFileSync17(tmp, single);
16448
16988
  argvOut.push("--metadata-raw-file", tmp);
16449
16989
  } else {
16450
16990
  if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
16451
- writeFileSync15(tmp, JSON.stringify(parts));
16991
+ writeFileSync17(tmp, JSON.stringify(parts));
16452
16992
  argvOut.push("--metadata-raw-parts-file", tmp);
16453
16993
  }
16454
16994
  }
@@ -16480,16 +17020,30 @@ var init_server = __esm({
16480
17020
  {
16481
17021
  name: "tendril_doctor",
16482
17022
  annotations: { readOnlyHint: true },
16483
- description: "Machine readiness + version status in one shot: installed vs latest version (with publish date and update remediation), browser identity, font-cache state, Figma desktop MCP reachability. Use to self-diagnose before recording/scoring, or whenever versions are in question. Exit 1 = something not ready; the report says exactly what and how to fix it.",
17023
+ description: "Machine readiness + version status in one shot: installed vs latest version (with publish date and update remediation), browser identity, font-cache state, Figma desktop MCP reachability, and whether this machine holds a LIVE portal session (checked against the portal itself; informational \u2014 publishing needs it, verification never does; when absent, offer tendril_login). Use to self-diagnose before recording/scoring, or whenever versions are in question. Exit 1 = something not ready; the report says exactly what and how to fix it.",
16484
17024
  schema: z14.object({}),
16485
17025
  argv: () => ["doctor"]
16486
17026
  },
16487
17027
  {
16488
- name: "tendril_record_next",
16489
- annotations: { readOnlyHint: true },
16490
- description: "Get the next pending recording instruction (which Figma MCP tool to call for which node, and how to save it). RARELY NEEDED: every ingest/fetch response already carries `next` \u2014 use this only to resume an interrupted session. The full queue is known from plan, so independent reps may be recorded in any order (and in parallel).",
16491
- schema: z14.object({ setDir: str("recording set directory") }),
16492
- argv: (i) => ["record", "next", "--set", i["setDir"]]
17028
+ name: "tendril_login",
17029
+ description: "Connect this machine to the user's Tendril portal account WITHOUT the user touching a terminal \u2014 phase one of the browser-approve sign-in. Starts the handshake, opens their browser to the approve page, and returns a verification link plus a short code. RELAY BOTH to the user verbatim: they click Approve on the page, and they must check the page shows EXACTLY this code (that check is the phishing defence \u2014 an attacker can send someone else's approve link). Then call tendril_login_wait to finish. Offer this whenever tendril_doctor reports no portal session and the user wants to publish or share; publishing needs it, verification never does.",
17030
+ schema: z14.object({
17031
+ portal: optStr("portal origin override for self-hosted portals (defaults to https://app.trytendril.com)")
17032
+ }),
17033
+ argv: (i) => ["login", "--device-start", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
17034
+ },
17035
+ {
17036
+ name: "tendril_login_wait",
17037
+ description: "Phase two of the browser-approve sign-in: waits (up to ten minutes, with live progress) for the user to click Approve on the page tendril_login opened, then stores the session on this machine. Call it right after relaying the link and code. A denial, a lapse, and success each come back as their own sentence; confirm the result to the user, and on success they are ready to publish.",
17038
+ schema: z14.object({}),
17039
+ argv: () => ["login", "--device-wait"]
17040
+ },
17041
+ {
17042
+ name: "tendril_record_next",
17043
+ annotations: { readOnlyHint: true },
17044
+ description: "Get the next pending recording instruction (which Figma MCP tool to call for which node, and how to save it). RARELY NEEDED: every ingest/fetch response already carries `next` \u2014 use this only to resume an interrupted session. The full queue is known from plan, so independent reps may be recorded in any order (and in parallel).",
17045
+ schema: z14.object({ setDir: str("recording set directory") }),
17046
+ argv: (i) => ["record", "next", "--set", i["setDir"]]
16493
17047
  },
16494
17048
  {
16495
17049
  name: "tendril_record_fetch",
@@ -16525,14 +17079,14 @@ var init_server = __esm({
16525
17079
  const bridge = (label, single, parts) => {
16526
17080
  if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
16527
17081
  if (single === void 0 && parts === void 0) return;
16528
- const tmp = path46.join(mkdtempSync3(path46.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
17082
+ const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
16529
17083
  if (single !== void 0) {
16530
- writeFileSync15(tmp, single);
17084
+ writeFileSync17(tmp, single);
16531
17085
  argvOut.push(`--${label}-file`, tmp);
16532
17086
  } else {
16533
17087
  const blocks = parts;
16534
17088
  if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
16535
- writeFileSync15(tmp, JSON.stringify(blocks));
17089
+ writeFileSync17(tmp, JSON.stringify(blocks));
16536
17090
  argvOut.push(`--${label}-parts-file`, tmp);
16537
17091
  }
16538
17092
  };
@@ -16573,12 +17127,12 @@ var init_server = __esm({
16573
17127
  const file = i["file"];
16574
17128
  if ([text, texts, file].filter((x) => x !== void 0).length !== 1) throw new Error("pass exactly one of `text` (single-block response), `texts` (multi-block response), or `file` (a saved envelope)");
16575
17129
  if (file !== void 0) return [...base, "--file", file];
16576
- const tmp = path46.join(mkdtempSync3(path46.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
17130
+ const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
16577
17131
  if (text !== void 0) {
16578
- writeFileSync15(tmp, text);
17132
+ writeFileSync17(tmp, text);
16579
17133
  return [...base, "--file", tmp, "--raw"];
16580
17134
  }
16581
- writeFileSync15(tmp, JSON.stringify(texts));
17135
+ writeFileSync17(tmp, JSON.stringify(texts));
16582
17136
  return [...base, "--file", tmp, "--raw-parts"];
16583
17137
  }
16584
17138
  },
@@ -16735,13 +17289,13 @@ __export(permissions_exports, {
16735
17289
  runPermissions: () => runPermissions,
16736
17290
  writeSelection: () => writeSelection
16737
17291
  });
16738
- import { existsSync as existsSync38, mkdirSync as mkdirSync10, readFileSync as readFileSync33, writeFileSync as writeFileSync16 } from "node:fs";
16739
- import os8 from "node:os";
16740
- import path47 from "node:path";
17292
+ import { existsSync as existsSync39, mkdirSync as mkdirSync11, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
17293
+ import os9 from "node:os";
17294
+ import path49 from "node:path";
16741
17295
  function mergeAllowlist(file, entries, denyEntries = []) {
16742
17296
  let settings = {};
16743
- if (existsSync38(file) && readFileSync33(file, "utf8").trim() !== "") {
16744
- settings = JSON.parse(readFileSync33(file, "utf8"));
17297
+ if (existsSync39(file) && readFileSync36(file, "utf8").trim() !== "") {
17298
+ settings = JSON.parse(readFileSync36(file, "utf8"));
16745
17299
  if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
16746
17300
  }
16747
17301
  const permissions = settings["permissions"] ??= {};
@@ -16761,8 +17315,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
16761
17315
  }
16762
17316
  if (added.length > 0 || denyAdded.length > 0) {
16763
17317
  allow.push(...added);
16764
- mkdirSync10(path47.dirname(file), { recursive: true });
16765
- writeFileSync16(file, `${JSON.stringify(settings, null, 2)}
17318
+ mkdirSync11(path49.dirname(file), { recursive: true });
17319
+ writeFileSync18(file, `${JSON.stringify(settings, null, 2)}
16766
17320
  `);
16767
17321
  }
16768
17322
  return { added, alreadyPresent, denyAdded };
@@ -16826,7 +17380,7 @@ async function runPermissions(flags) {
16826
17380
  }
16827
17381
  if (flags.write) {
16828
17382
  const base = process.env["INIT_CWD"] ?? process.cwd();
16829
- const file = flags.user ? path47.join(os8.homedir(), ".claude", "settings.json") : path47.join(base, ".claude", "settings.local.json");
17383
+ const file = flags.user ? path49.join(os9.homedir(), ".claude", "settings.json") : path49.join(base, ".claude", "settings.local.json");
16830
17384
  const { entries, denyEntries } = writeSelection(result, flags.user === true);
16831
17385
  if (flags.dryRun) {
16832
17386
  emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
@@ -16975,24 +17529,51 @@ __export(inspect_exports, {
16975
17529
  INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
16976
17530
  runInspect: () => runInspect
16977
17531
  });
16978
- import { existsSync as existsSync39, readFileSync as readFileSync34, writeFileSync as writeFileSync17 } from "node:fs";
16979
- import path48 from "node:path";
17532
+ import { existsSync as existsSync40, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
17533
+ import path50 from "node:path";
17534
+ function readVerifyReport(evidenceDir) {
17535
+ const p = path50.join(evidenceDir, VERIFY_REPORT_FILENAME);
17536
+ if (!existsSync40(p)) return void 0;
17537
+ try {
17538
+ return JSON.parse(readFileSync37(p, "utf8"));
17539
+ } catch {
17540
+ return void 0;
17541
+ }
17542
+ }
17543
+ function verdictBlock(report) {
17544
+ if (report === void 0) {
17545
+ return `<div class="verdict none"><h2>No verdict on file</h2><p>This evidence carries no <code>${VERIFY_REPORT_FILENAME}</code>, so these images are unlabelled pixels \u2014 nothing here says what passed. Re-run <code>tendril verify</code> to write one.</p></div>`;
17546
+ }
17547
+ const buckets = /* @__PURE__ */ new Map();
17548
+ for (const c of report.configs) buckets.set(c.status, (buckets.get(c.status) ?? 0) + 1);
17549
+ const split = [...buckets.entries()].map(([k, n]) => `<span class="tier t-${esc(k)}">${n} ${esc(k)}</span>`).join("");
17550
+ const failed = report.behaviors.filter((b) => !b.pass);
17551
+ return `<div class="verdict ${report.verdict === "verified" ? "ok" : "bad"}"><h2>${esc(report.verdict)} <small>at bar \u201C${esc(report.targetBar)}\u201D</small></h2><p class="tiers">${split}<span class="tier">${report.behaviors.length - failed.length}/${report.behaviors.length} behaviors</span></p>` + (failed.length > 0 ? `<ul class="fails">${failed.map((b) => `<li><code>${esc(b.id)}</code>${b.detail !== void 0 ? ` \u2014 ${esc(b.detail)}` : ""}</li>`).join("")}</ul>` : "") + (report.verdictCaveats.length > 0 ? `<h3>What was never asked</h3><ul class="caveats">${report.verdictCaveats.map((c) => `<li>${esc(c)}</li>`).join("")}</ul>` : `<p class="caveats">No caveats: every question this bar asks was answered.</p>`) + `</div>`;
17552
+ }
17553
+ function scoreLine(report, rep) {
17554
+ const c = report?.configs.find((x) => x.rep === rep);
17555
+ if (c === void 0) return "";
17556
+ const ex = c.exact !== void 0 ? ` <span class="exact">measured ${c.exact.similarity.toFixed(6)} / ${c.exact.inkRecall.toFixed(6)}</span>` : "";
17557
+ const why = Array.isArray(c.demotedBy) && c.demotedBy.length > 0 ? `<span class="demoted">demoted: ${esc(c.demotedBy.join("; "))}</span>` : "";
17558
+ const err = typeof c.error === "string" ? `<span class="demoted">${esc(c.error)}</span>` : "";
17559
+ return `<p class="scores"><span class="tier t-${esc(c.status)}">${esc(c.status)}</span> sim ${c.similarity} \xB7 ink ${c.inkRecall}${ex} ${why} ${err}</p>`;
17560
+ }
16980
17561
  async function runInspect(opts) {
16981
17562
  if (opts.describe) {
16982
17563
  printDescription(INSPECT_DESCRIPTION);
16983
17564
  return;
16984
17565
  }
16985
- const bundleDir = path48.resolve(opts.bundleDir);
16986
- const evidenceDir = path48.join(bundleDir, "verify-evidence");
16987
- const manifestPath2 = path48.join(bundleDir, "component.json");
16988
- if (!existsSync39(evidenceDir) || !existsSync39(manifestPath2)) {
17566
+ const bundleDir = path50.resolve(opts.bundleDir);
17567
+ const evidenceDir = path50.join(bundleDir, "verify-evidence");
17568
+ const manifestPath2 = path50.join(bundleDir, "component.json");
17569
+ if (!existsSync40(evidenceDir) || !existsSync40(manifestPath2)) {
16989
17570
  fail(opts, ExitCode.InputValidation, {
16990
- error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync39(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
17571
+ error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync40(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
16991
17572
  code: "no-evidence",
16992
17573
  remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
16993
17574
  });
16994
17575
  }
16995
- const { manifest } = readBundleManifest(readFileSync34(manifestPath2, "utf8"));
17576
+ const { manifest } = readBundleManifest(readFileSync37(manifestPath2, "utf8"));
16996
17577
  if (manifest === void 0) {
16997
17578
  fail(opts, ExitCode.InputValidation, {
16998
17579
  error: "component.json did not parse as a bundle manifest",
@@ -17000,8 +17581,9 @@ async function runInspect(opts) {
17000
17581
  remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
17001
17582
  });
17002
17583
  }
17003
- const setDir = path48.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17004
- const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync39(path48.join(evidenceDir, `${rep}-ref.png`)) && existsSync39(path48.join(evidenceDir, `${rep}-render.png`)));
17584
+ const setDir = path50.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17585
+ const report = readVerifyReport(evidenceDir);
17586
+ const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync40(path50.join(evidenceDir, `${rep}-ref.png`)) && existsSync40(path50.join(evidenceDir, `${rep}-render.png`)));
17005
17587
  if (reps.length === 0) {
17006
17588
  fail(opts, ExitCode.InputValidation, {
17007
17589
  error: "verify-evidence holds no ref/render pairs for this bundle's configs",
@@ -17012,15 +17594,15 @@ async function runInspect(opts) {
17012
17594
  let crops = 0;
17013
17595
  const sections = [];
17014
17596
  for (const rep of reps) {
17015
- const ref = new Uint8Array(readFileSync34(path48.join(evidenceDir, `${rep}-ref.png`)));
17016
- const render = new Uint8Array(readFileSync34(path48.join(evidenceDir, `${rep}-render.png`)));
17597
+ const ref = new Uint8Array(readFileSync37(path50.join(evidenceDir, `${rep}-ref.png`)));
17598
+ const render = new Uint8Array(readFileSync37(path50.join(evidenceDir, `${rep}-render.png`)));
17017
17599
  const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
17018
17600
  const cells = [];
17019
17601
  for (const [i, n] of nodes.entries()) {
17020
17602
  const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
17021
17603
  try {
17022
- writeFileSync17(path48.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
17023
- writeFileSync17(path48.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
17604
+ writeFileSync19(path50.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
17605
+ writeFileSync19(path50.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
17024
17606
  } catch {
17025
17607
  continue;
17026
17608
  }
@@ -17030,11 +17612,15 @@ async function runInspect(opts) {
17030
17612
  );
17031
17613
  }
17032
17614
  sections.push(
17033
- `<section><h2>${esc(rep)}</h2><div class="full"><span><em>recorded</em><img src="./${rep}-ref.png"></span><span><em>rendered</em><img src="./${rep}-render.png"></span><span><em>diff</em><img src="./${rep}-diff.png"></span></div>` + (cells.length > 0 ? `<div class="grid">${cells.join("")}</div>` : `<p class="none">no small recorded nodes in this config's sweep</p>`) + `</section>`
17615
+ `<section><h2>${esc(rep)}</h2>` + scoreLine(report, rep) + `<div class="full"><span><em>recorded</em><img src="./${rep}-ref.png"></span><span><em>rendered</em><img src="./${rep}-render.png"></span><span><em>diff</em><img src="./${rep}-diff.png"></span></div>` + (cells.length > 0 ? `<div class="grid">${cells.join("")}</div>` : `<p class="none">no small recorded nodes in this config's sweep</p>`) + `</section>`
17034
17616
  );
17035
17617
  }
17036
- const sheet = path48.join(evidenceDir, "inspect.html");
17037
- writeFileSync17(
17618
+ for (const c of report?.configs ?? []) {
17619
+ if (reps.includes(c.rep)) continue;
17620
+ sections.push(`<section class="missing"><h2>${esc(c.rep)}</h2>${scoreLine(report, c.rep)}<p class="none">No evidence images for this config \u2014 it was scored, but nothing was captured to look at.</p></section>`);
17621
+ }
17622
+ const sheet = path50.join(evidenceDir, "inspect.html");
17623
+ writeFileSync19(
17038
17624
  sheet,
17039
17625
  `<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
17040
17626
  body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
@@ -17043,11 +17629,23 @@ h1{font-size:20px} h2{font-size:16px;border-top:1px solid #ddd;padding-top:16px}
17043
17629
  .full img{max-width:400px;border:1px solid #ccc} .pair img{border:1px solid #ccc;image-rendering:pixelated}
17044
17630
  figure{margin:0 0 16px} figcaption{margin-bottom:4px} em{display:block;color:#666;font-style:normal;font-size:12px}
17045
17631
  .grid{display:flex;flex-wrap:wrap;gap:20px;margin-top:12px} .none{color:#666}
17046
- </style><h1>${esc(manifest.name)} \u2014 detail sheet (small recorded nodes, recorded vs rendered)</h1>
17047
- <p>Every crop is a recorded node small enough that global metrics weight it as a rounding error.
17632
+ .verdict{border:1px solid #ddd;border-left-width:5px;padding:12px 16px;margin:16px 0;background:#fafafa}
17633
+ .verdict.ok{border-left-color:#1a7f37} .verdict.bad{border-left-color:#b35900} .verdict.none{border-left-color:#999}
17634
+ .verdict h2{border:0;padding:0;margin:0 0 8px} .verdict h3{font-size:13px;margin:12px 0 4px;color:#444}
17635
+ .verdict small{font-weight:400;color:#666}
17636
+ .tiers{margin:0;display:flex;gap:8px;flex-wrap:wrap}
17637
+ .tier{display:inline-block;padding:1px 8px;border:1px solid #ccc;border-radius:10px;font-size:12px;background:#fff}
17638
+ .t-certified{border-color:#1a7f37;color:#1a7f37} .t-pass{border-color:#8a6d00;color:#8a6d00} .t-fail{border-color:#b3261e;color:#b3261e}
17639
+ .caveats,.fails{margin:4px 0 0;padding-left:20px;color:#444} .caveats li,.fails li{margin:2px 0}
17640
+ .scores{margin:4px 0 10px;color:#333} .exact{color:#666;font-size:12px} .demoted{color:#b3261e;font-size:12px;margin-left:8px}
17641
+ .missing{opacity:.85} .legend{white-space:pre-wrap;font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;background:#fafafa;border:1px solid #ddd;padding:12px;overflow-x:auto}
17642
+ </style><h1>${esc(manifest.name)} \u2014 verdict and evidence</h1>
17643
+ ${verdictBlock(report)}
17644
+ <p>Below, every crop is a recorded node small enough that global metrics weight it as a rounding error.
17048
17645
  Scan the pairs: anything present on the left and missing/invisible on the right is a defect,
17049
17646
  whatever the scores said. Verdicts come from <code>tendril verify</code> \u2014 this sheet only shows.</p>
17050
17647
  ${sections.join("\n")}
17648
+ <section><h2>Diff colours</h2><div class="legend">${esc(DIFF_LEGEND_TEXT)}</div></section>
17051
17649
  `
17052
17650
  );
17053
17651
  emitData(opts, { sheet, configs: reps.length, crops }, () => {
@@ -17087,6 +17685,694 @@ var init_inspect = __esm({
17087
17685
  }
17088
17686
  });
17089
17687
 
17688
+ // packages/cli/src/commands/login.ts
17689
+ var login_exports = {};
17690
+ __export(login_exports, {
17691
+ DEFAULT_PORTAL_ORIGIN: () => DEFAULT_PORTAL_ORIGIN,
17692
+ runLogin: () => runLogin,
17693
+ runLogout: () => runLogout
17694
+ });
17695
+ import { spawn } from "node:child_process";
17696
+ import { existsSync as existsSync41, mkdirSync as mkdirSync12, readFileSync as readFileSync38, rmSync as rmSync6, writeFileSync as writeFileSync20 } from "node:fs";
17697
+ import path51 from "node:path";
17698
+ import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
17699
+ async function runLogin(opts, deps) {
17700
+ const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
17701
+ if (!isSecureOrigin(origin)) {
17702
+ fail(opts, ExitCode.InputValidation, {
17703
+ error: `${origin} is not https, so a session token sent there would travel in the clear`,
17704
+ code: "portal-not-https",
17705
+ remediation: "Use the https:// address of your portal. Only 127.0.0.1 and localhost are exempt, for local development."
17706
+ });
17707
+ }
17708
+ if (opts.deviceStart === true) {
17709
+ await deviceStartPhase(opts, origin, deps ?? realDeps());
17710
+ return;
17711
+ }
17712
+ if (opts.deviceWait === true) {
17713
+ await deviceWaitPhase(opts, deps ?? realDeps());
17714
+ return;
17715
+ }
17716
+ if (process.stdin.isTTY === true && opts.paste !== true) {
17717
+ await deviceLogin(opts, origin, deps ?? realDeps());
17718
+ return;
17719
+ }
17720
+ await pasteLogin(opts, origin);
17721
+ }
17722
+ async function deviceLogin(opts, origin, deps) {
17723
+ const started = await startHandshake(opts, origin, deps);
17724
+ process.stderr.write(`
17725
+ Open this link to approve the sign-in:
17726
+
17727
+ ${started.verificationUrl}
17728
+
17729
+ `);
17730
+ process.stderr.write(` The page must show this code: ${started.userCode}
17731
+ `);
17732
+ process.stderr.write(` Approve it only if this terminal is yours.
17733
+
17734
+ `);
17735
+ deps.openBrowser(started.verificationUrl);
17736
+ process.stderr.write(` Waiting for the browser (about ten minutes before this code lapses) `);
17737
+ const outcome = await waitForDecision(opts, origin, deps, started, () => process.stderr.write("."));
17738
+ process.stderr.write("\n");
17739
+ settleDecision(opts, origin, outcome);
17740
+ }
17741
+ async function waitForDecision(opts, origin, deps, started, onTick) {
17742
+ const interval = Math.max(1, started.intervalSeconds);
17743
+ const attempts = Math.ceil(660 / interval);
17744
+ for (let n = 0; n < attempts; n += 1) {
17745
+ await deps.sleep(interval);
17746
+ const state = await pollHandshake(opts, origin, deps, started.deviceCode);
17747
+ if (state.status === "pending") {
17748
+ onTick();
17749
+ continue;
17750
+ }
17751
+ if (state.status === "approved") return state;
17752
+ return { status: state.status };
17753
+ }
17754
+ return { status: "gave-up" };
17755
+ }
17756
+ function settleDecision(opts, origin, outcome) {
17757
+ switch (outcome.status) {
17758
+ case "approved":
17759
+ writeStoredSession({ origin, token: outcome.token });
17760
+ emitData(opts, { origin, storedAt: sessionPath(), via: "device" }, () => {
17761
+ process.stdout.write(`signed in to ${origin}
17762
+ `);
17763
+ process.stdout.write(` token stored at ${sessionPath()} (readable only by you)
17764
+ `);
17765
+ });
17766
+ return;
17767
+ case "denied":
17768
+ fail(opts, ExitCode.General, {
17769
+ error: "the sign-in was denied in the browser",
17770
+ code: "login-denied",
17771
+ remediation: `If that was not you saying no, run ${tendrilCommand("login")} again and approve the fresh code.`
17772
+ });
17773
+ break;
17774
+ case "expired":
17775
+ case "gave-up":
17776
+ fail(opts, ExitCode.General, {
17777
+ error: outcome.status === "expired" ? "the sign-in code lapsed before anyone approved it \u2014 they live ten minutes" : "gave up waiting for the browser approval",
17778
+ code: "login-expired",
17779
+ remediation: `Run ${tendrilCommand("login")} again for a fresh code.`
17780
+ });
17781
+ break;
17782
+ default:
17783
+ fail(opts, ExitCode.General, {
17784
+ error: "the portal no longer recognises this sign-in attempt",
17785
+ code: "login-unknown",
17786
+ remediation: `Run ${tendrilCommand("login")} again. If this repeats, the approval may be racing another terminal \u2014 approve only one at a time.`
17787
+ });
17788
+ }
17789
+ }
17790
+ function pendingLoginPath() {
17791
+ return path51.join(path51.dirname(sessionPath()), "pending-login.json");
17792
+ }
17793
+ async function deviceStartPhase(opts, origin, deps) {
17794
+ const started = await startHandshake(opts, origin, deps);
17795
+ const file = pendingLoginPath();
17796
+ mkdirSync12(path51.dirname(file), { recursive: true });
17797
+ writeFileSync20(file, `${JSON.stringify({ origin, ...started }, null, 2)}
17798
+ `, { mode: 384 });
17799
+ deps.openBrowser(started.verificationUrl);
17800
+ emitData(
17801
+ opts,
17802
+ {
17803
+ verificationUrl: started.verificationUrl,
17804
+ userCode: started.userCode,
17805
+ expiresAt: started.expiresAt,
17806
+ origin,
17807
+ next: "Show the user the link and the code \u2014 they approve in the browser, and must check the page shows this exact code. Then run login --device-wait (the tendril_login_wait tool) to finish."
17808
+ },
17809
+ () => {
17810
+ process.stdout.write(`open ${started.verificationUrl}
17811
+ `);
17812
+ process.stdout.write(` the page must show: ${started.userCode}
17813
+ `);
17814
+ process.stdout.write(` then: ${tendrilCommand("login --device-wait")}
17815
+ `);
17816
+ }
17817
+ );
17818
+ }
17819
+ async function deviceWaitPhase(opts, deps) {
17820
+ const file = pendingLoginPath();
17821
+ let pending;
17822
+ if (existsSync41(file)) {
17823
+ try {
17824
+ const parsed = JSON.parse(readFileSync38(file, "utf8"));
17825
+ if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
17826
+ pending = parsed;
17827
+ }
17828
+ } catch {
17829
+ }
17830
+ }
17831
+ if (pending === void 0) {
17832
+ fail(opts, ExitCode.InputValidation, {
17833
+ error: "there is no sign-in waiting to finish",
17834
+ code: "no-pending-login",
17835
+ remediation: `Start one first: ${tendrilCommand("login --device-start")} (the tendril_login tool).`
17836
+ });
17837
+ }
17838
+ const done = () => rmSync6(file, { force: true });
17839
+ const total = Math.ceil(660 / Math.max(1, pending.intervalSeconds));
17840
+ let ticks = 0;
17841
+ const outcome = await waitForDecision(opts, pending.origin, deps, pending, () => {
17842
+ ticks += 1;
17843
+ emitProgress(ticks, total, "waiting for the browser approval");
17844
+ });
17845
+ done();
17846
+ settleDecision(opts, pending.origin, outcome);
17847
+ }
17848
+ async function startHandshake(opts, origin, deps) {
17849
+ let response;
17850
+ try {
17851
+ response = await deps.fetch(`${origin}/api/device`, { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
17852
+ } catch (error) {
17853
+ return unreachable(opts, origin, error.message);
17854
+ }
17855
+ if (!response.ok) return unreachable(opts, origin, `the portal answered ${String(response.status)}`);
17856
+ const body = await response.json();
17857
+ if (typeof body.deviceCode !== "string" || typeof body.userCode !== "string" || typeof body.verificationUrl !== "string" || typeof body.intervalSeconds !== "number" || typeof body.expiresAt !== "string") {
17858
+ return unreachable(opts, origin, "the portal's answer was not a device handshake");
17859
+ }
17860
+ return body;
17861
+ }
17862
+ async function pollHandshake(opts, origin, deps, deviceCode) {
17863
+ let response;
17864
+ try {
17865
+ response = await deps.fetch(`${origin}/api/device/token`, {
17866
+ method: "POST",
17867
+ headers: { "content-type": "application/json" },
17868
+ body: JSON.stringify({ deviceCode })
17869
+ });
17870
+ } catch {
17871
+ return { status: "pending" };
17872
+ }
17873
+ if (!response.ok) return { status: "pending" };
17874
+ const body = await response.json();
17875
+ if (body.status === "approved" && typeof body.token === "string") return { status: "approved", token: body.token };
17876
+ if (body.status === "denied" || body.status === "expired" || body.status === "unknown") return { status: body.status };
17877
+ return { status: "pending" };
17878
+ }
17879
+ function unreachable(opts, origin, detail) {
17880
+ fail(opts, ExitCode.General, {
17881
+ error: `could not start a sign-in with ${origin}: ${detail}`,
17882
+ code: "portal-unreachable",
17883
+ remediation: "Check your connection and try again. If your portal is self-hosted, confirm the --to URL."
17884
+ });
17885
+ }
17886
+ function realDeps() {
17887
+ return {
17888
+ fetch: globalThis.fetch.bind(globalThis),
17889
+ sleep: (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1e3)),
17890
+ openBrowser: (url) => {
17891
+ const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
17892
+ try {
17893
+ spawn(cmd, args, { stdio: "ignore", detached: true }).on("error", () => void 0).unref();
17894
+ } catch {
17895
+ }
17896
+ }
17897
+ };
17898
+ }
17899
+ async function pasteLogin(opts, origin) {
17900
+ const token = await readToken(opts);
17901
+ if (token === void 0 || token.trim() === "") {
17902
+ fail(opts, ExitCode.InputValidation, {
17903
+ error: "no token was given",
17904
+ code: "no-token",
17905
+ remediation: `Paste the token when prompted, or pipe it: \`echo "<token>" | ${tendrilCommand(`login --to ${origin}`)}\`.`
17906
+ });
17907
+ }
17908
+ const trimmed = token.trim();
17909
+ if (!/^[A-Za-z0-9_-]{16,512}$/.test(trimmed)) {
17910
+ fail(opts, ExitCode.InputValidation, {
17911
+ error: "that does not look like a Tendril session token",
17912
+ code: "malformed-token",
17913
+ remediation: "Tokens are a single line of letters, digits, hyphens and underscores. Check for a stray space or a truncated paste."
17914
+ });
17915
+ }
17916
+ writeStoredSession({ origin, token: trimmed });
17917
+ emitData(opts, { origin, storedAt: sessionPath(), via: "paste" }, () => {
17918
+ process.stdout.write(`signed in to ${origin}
17919
+ `);
17920
+ process.stdout.write(` token stored at ${sessionPath()} (readable only by you)
17921
+ `);
17922
+ });
17923
+ }
17924
+ async function readToken(opts) {
17925
+ if (process.stdin.isTTY === true) {
17926
+ const entered = await password2({ message: "Paste your Tendril portal token (it will not be shown)" });
17927
+ if (isCancel3(entered)) {
17928
+ fail(opts, ExitCode.General, { error: "cancelled", code: "cancelled", remediation: "Run it again when you have the token." });
17929
+ }
17930
+ return entered;
17931
+ }
17932
+ const chunks = [];
17933
+ for await (const chunk of process.stdin) chunks.push(chunk);
17934
+ return Buffer.concat(chunks).toString("utf8").split("\n")[0];
17935
+ }
17936
+ function runLogout(opts) {
17937
+ const stored = readStoredSession();
17938
+ if (stored === void 0) {
17939
+ emitData(opts, { signedOut: true, wasSignedIn: false }, () => process.stdout.write("you were not signed in\n"));
17940
+ return;
17941
+ }
17942
+ clearStoredSession();
17943
+ emitData(opts, { signedOut: true, wasSignedIn: true, origin: stored.origin }, () => {
17944
+ process.stdout.write(`this machine has forgotten its session for ${stored.origin}
17945
+ `);
17946
+ process.stdout.write(" the session itself is still valid \u2014 sign out everywhere from the portal to end it\n");
17947
+ });
17948
+ }
17949
+ var DEFAULT_PORTAL_ORIGIN;
17950
+ var init_login = __esm({
17951
+ "packages/cli/src/commands/login.ts"() {
17952
+ "use strict";
17953
+ init_src3();
17954
+ init_invocation();
17955
+ init_output();
17956
+ init_publish_client();
17957
+ DEFAULT_PORTAL_ORIGIN = "https://app.trytendril.com";
17958
+ }
17959
+ });
17960
+
17961
+ // packages/cli/src/commands/share.ts
17962
+ var share_exports = {};
17963
+ __export(share_exports, {
17964
+ runShare: () => runShare
17965
+ });
17966
+ async function runShare(opts) {
17967
+ const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
17968
+ if (origin === "") {
17969
+ fail(opts, ExitCode.InputValidation, {
17970
+ error: "no portal to share from",
17971
+ code: "no-portal-configured",
17972
+ remediation: `Pass \`--to <url>\` or set TENDRIL_PORTAL_URL.`
17973
+ });
17974
+ }
17975
+ if (!isSecureOrigin(origin)) {
17976
+ fail(opts, ExitCode.InputValidation, {
17977
+ error: `${origin} is not https, so a session token sent there would travel in the clear`,
17978
+ code: "portal-not-https",
17979
+ remediation: "Use the https:// address of your portal. Only 127.0.0.1 and localhost are exempt, for local development."
17980
+ });
17981
+ }
17982
+ const found = tokenFor(origin);
17983
+ if (!found.ok) {
17984
+ fail(opts, ExitCode.Auth, {
17985
+ error: found.reason === "origin-mismatch" ? `the session available here belongs to ${found.boundTo}, not ${origin}` : `no session for ${origin}`,
17986
+ code: found.reason === "origin-mismatch" ? "session-belongs-to-another-portal" : "not-signed-in",
17987
+ remediation: `Run \`${tendrilCommand(`login --to ${origin}`)}\`.`
17988
+ });
17989
+ }
17990
+ const client = new HttpPublishClient({ origin, token: found.token });
17991
+ if (opts.list === true) {
17992
+ const listed = await client.listShareLinks({ publicationId: opts.publicationId });
17993
+ if (!listed.ok) refuse(opts, listed);
17994
+ emitData(opts, listed.value, () => {
17995
+ if (listed.value.links.length === 0) {
17996
+ process.stdout.write("no links have been issued for this publication\n");
17997
+ return;
17998
+ }
17999
+ process.stdout.write(`${String(listed.value.links.length)} link(s) for this publication:
18000
+ `);
18001
+ for (const link of listed.value.links) {
18002
+ const ended = link.revoked_at !== null ? ` REVOKED ${link.revoked_at}` : link.expires_at !== null ? ` expires ${link.expires_at}` : " open-ended";
18003
+ process.stdout.write(` ${link.id}${ended}${link.recipient_email === null ? "" : ` \u2014 for ${link.recipient_email}`}
18004
+ `);
18005
+ }
18006
+ process.stdout.write(" (the links themselves are not stored \u2014 only their digests, so a lost link is a new link)\n");
18007
+ });
18008
+ return;
18009
+ }
18010
+ if (opts.revoke !== void 0) {
18011
+ const revoked = await client.revokeShareLink({ publicationId: opts.publicationId, shareLinkId: opts.revoke });
18012
+ if (!revoked.ok) refuse(opts, revoked);
18013
+ emitData(opts, revoked.value, () => {
18014
+ process.stdout.write(`revoked ${opts.revoke ?? ""}
18015
+ `);
18016
+ process.stdout.write(" anyone holding that link now gets told it was revoked, not that it never existed\n");
18017
+ });
18018
+ return;
18019
+ }
18020
+ const expiresAt = resolveExpiry(opts);
18021
+ const issued = await client.issueShareLink({
18022
+ publicationId: opts.publicationId,
18023
+ expiresAt,
18024
+ recipientEmail: opts.recipient ?? null
18025
+ });
18026
+ if (!issued.ok) refuse(opts, issued);
18027
+ emitData(opts, issued.value, () => {
18028
+ process.stdout.write(`${issued.value.url}
18029
+
18030
+ `);
18031
+ process.stdout.write(" Anyone with this link can read this one publication and its evidence.\n");
18032
+ process.stdout.write(" It is not tied to a person and needs no account.\n");
18033
+ process.stdout.write(
18034
+ issued.value.expiresAt === null ? " It does not expire. Revoke it with --revoke when you are done.\n" : ` It stops working at ${issued.value.expiresAt}.
18035
+ `
18036
+ );
18037
+ process.stdout.write(" This is the only time the link is shown \u2014 only its digest is stored.\n");
18038
+ });
18039
+ }
18040
+ function resolveExpiry(opts) {
18041
+ if (opts.expires === void 0) return null;
18042
+ const days = Number(opts.expires);
18043
+ if (Number.isInteger(days) && days > 0) {
18044
+ return new Date(Date.now() + days * 24 * 60 * 60 * 1e3).toISOString().replace(/\.\d{3}Z$/, (m) => m);
18045
+ }
18046
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(opts.expires)) return opts.expires;
18047
+ fail(opts, ExitCode.InputValidation, {
18048
+ error: `--expires takes a number of days (e.g. 7) or an ISO-8601 UTC timestamp with milliseconds \u2014 got ${JSON.stringify(opts.expires)}`,
18049
+ code: "bad-expiry",
18050
+ remediation: "Try `--expires 7` for a week, or omit it for a link that does not expire."
18051
+ });
18052
+ }
18053
+ function refuse(opts, sent) {
18054
+ fail(opts, sent.status === 401 ? ExitCode.Auth : ExitCode.General, {
18055
+ error: sent.refusal,
18056
+ code: "share-refused",
18057
+ remediation: sent.status === 404 ? "Check the publication id \u2014 only its owner can share it." : `Fix what is named above and run \`${tendrilCommand(`share ${opts.publicationId}`)}\` again.`
18058
+ });
18059
+ }
18060
+ var init_share = __esm({
18061
+ "packages/cli/src/commands/share.ts"() {
18062
+ "use strict";
18063
+ init_src3();
18064
+ init_invocation();
18065
+ init_output();
18066
+ init_publish_client();
18067
+ }
18068
+ });
18069
+
18070
+ // packages/cli/src/commands/publish.ts
18071
+ var publish_exports = {};
18072
+ __export(publish_exports, {
18073
+ runPublish: () => runPublish
18074
+ });
18075
+ import { existsSync as existsSync42, readFileSync as readFileSync39 } from "node:fs";
18076
+ import path52 from "node:path";
18077
+ async function runPublish(opts) {
18078
+ const bundleDir = path52.resolve(opts.bundleDir);
18079
+ const bundle = readBundle(opts, bundleDir);
18080
+ const report = bundle.report;
18081
+ if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
18082
+ fail(opts, ExitCode.InputValidation, {
18083
+ error: `${VERIFY_REPORT_FILENAME} does not state the ruler's exit code, and an absent exit code is not a passing one`,
18084
+ code: "report-has-no-exit-code",
18085
+ remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` with a current CLI \u2014 this report predates the field.`
18086
+ });
18087
+ }
18088
+ if (report["rulerExit"] !== 0) {
18089
+ const why = typeof report["rulerRefusal"] === "string" ? ` \u2014 ${report["rulerRefusal"]}` : "";
18090
+ fail(opts, ExitCode.VerificationFailed, {
18091
+ error: `the ruler refused this run (exit ${String(report["rulerExit"])}), so it has nothing to publish${why}`,
18092
+ code: "run-refused-by-ruler",
18093
+ remediation: `Fix what the report names, re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` until it passes, then publish. A declined verdict never becomes a page.`
18094
+ });
18095
+ }
18096
+ const undisclosed = undisclosedTrustFacts(report);
18097
+ if (undisclosed.length > 0) {
18098
+ fail(opts, ExitCode.InputValidation, {
18099
+ error: `this report carries a disclosure a published page cannot yet show, and a verdict shown without it is worse than no page: ${undisclosed.map((f) => `${f.pointer} \u2014 ${f.consequence}`).join("; ")}`,
18100
+ code: "report-carries-an-unrenderable-disclosure",
18101
+ remediation: "Resolve what the disclosure names \u2014 re-record so the set matches the bundle's stamp, or resolve the substituted font families \u2014 then re-verify and publish the clean run."
18102
+ });
18103
+ }
18104
+ if (scoredRecordingSetHash(report) === void 0) {
18105
+ fail(opts, ExitCode.InputValidation, {
18106
+ error: "this report does not name the recording set these scores were measured against",
18107
+ code: "report-names-no-recording-set",
18108
+ remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` with a current CLI \u2014 the set identity rides the report.`
18109
+ });
18110
+ }
18111
+ const componentName = (opts.name ?? bundle.manifest.name).trim();
18112
+ const surface = classifyBundleSurface(bundle.files, { entry: bundle.manifest.entry });
18113
+ if (surface.unknown.length > 0) {
18114
+ fail(opts, ExitCode.InputValidation, {
18115
+ error: `this bundle carries files a portal does not recognise, and publishing guesses at nothing: ${surface.unknown.join(", ")}`,
18116
+ code: "bundle-carries-unrecognised-files",
18117
+ remediation: `Remove them from ${opts.bundleDir}, or re-emit the bundle. Publishing them would ship something nobody reviewed; dropping them silently would ship evidence with a hole in it.`
18118
+ });
18119
+ }
18120
+ const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
18121
+ if (sheetEntry !== void 0) {
18122
+ const missingCrops = missingInspectCrops(
18123
+ readFileSync39(path52.join(bundleDir, sheetEntry.path), "utf8"),
18124
+ surface.published.map((p) => p.path)
18125
+ );
18126
+ if (missingCrops.length > 0) {
18127
+ fail(opts, ExitCode.InputValidation, {
18128
+ error: `this bundle's inspect sheet references images it does not carry, and half a sheet reads as a broken component: ${missingCrops.join(", ")}`,
18129
+ code: "inspect-sheet-without-its-crops",
18130
+ remediation: `Re-run \`${tendrilCommand(`inspect ${opts.bundleDir}`)}\` to rebuild the sheet and its crops together, or delete the sheet and publish without it.`
18131
+ });
18132
+ }
18133
+ }
18134
+ if (surface.missingRequired.length > 0) {
18135
+ fail(opts, ExitCode.InputValidation, {
18136
+ error: `this bundle is missing what a publishable bundle cannot be without: ${surface.missingRequired.join(", ")}`,
18137
+ code: "bundle-missing-required-role",
18138
+ remediation: `Run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` \u2014 the evidence a page shows is written by that run. Required: ${REQUIRED_ROLES.join(", ")}.`
18139
+ });
18140
+ }
18141
+ const recorded = readScoredFiles(report);
18142
+ if (recorded === void 0) {
18143
+ fail(opts, ExitCode.InputValidation, {
18144
+ error: `${VERIFY_REPORT_FILENAME} does not record the files this run was scored beside, so nothing ties this bundle's bytes to its verdict`,
18145
+ code: "report-records-no-file-digests",
18146
+ remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` with a current CLI \u2014 this report predates the field.`
18147
+ });
18148
+ } else {
18149
+ const delta = compareScoredFiles(recorded, digestScoredFiles(bundleDir, bundle.manifest.entry));
18150
+ const disagreements = [
18151
+ ...delta.changed.map((p) => `${p} (bytes differ from the scored run)`),
18152
+ ...delta.missing.map((p) => `${p} (scored, no longer in the bundle)`),
18153
+ ...delta.unscored.map((p) => `${p} (in the bundle, never scored)`)
18154
+ ];
18155
+ if (disagreements.length > 0) {
18156
+ fail(opts, ExitCode.InputValidation, {
18157
+ error: `this bundle is not the one the ruler scored: ${disagreements.join("; ")}`,
18158
+ code: "bundle-disagrees-with-scored-files",
18159
+ remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` so the verdict describes these bytes. A page must never show code the ruler never saw.`
18160
+ });
18161
+ }
18162
+ }
18163
+ const figmaFile = bundle.manifest.provenance.recordingSet.figmaFile ?? null;
18164
+ if (opts.dryRun) {
18165
+ emitData(
18166
+ opts,
18167
+ {
18168
+ bundleDir,
18169
+ componentName,
18170
+ figmaFile,
18171
+ entry: bundle.manifest.entry,
18172
+ publishes: surface.published,
18173
+ excluded: surface.excluded,
18174
+ rulerVersion: report["environment"]?.["ruler"] ?? null,
18175
+ wouldPublish: true
18176
+ },
18177
+ () => {
18178
+ process.stdout.write(`${componentName} would publish ${String(surface.published.length)} files:
18179
+ `);
18180
+ for (const entry of surface.published) process.stdout.write(` ${entry.path} (${entry.role})
18181
+ `);
18182
+ for (const left of surface.excluded) process.stdout.write(` \u2014 leaving ${left.path} behind (${left.reason})
18183
+ `);
18184
+ }
18185
+ );
18186
+ return;
18187
+ }
18188
+ const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
18189
+ const client = opts.client ?? httpClient(opts, origin);
18190
+ if (opts.confirmPublish === true && process.stdin.isTTY !== true) {
18191
+ fail(opts, ExitCode.ConfirmationRequired, {
18192
+ error: "--confirm-publish requires an interactive terminal \u2014 putting a component on the internet is a human-only decision",
18193
+ code: "publish-confirmation-not-interactive",
18194
+ remediation: `A human runs \`${tendrilCommand(`publish ${opts.bundleDir} --confirm-publish`)}\` in their own terminal. Agents: show this to your operator instead of confirming it.`
18195
+ });
18196
+ }
18197
+ if (opts.acceptTerms === true) {
18198
+ const accepted = await client.acceptTerms({ figmaFile });
18199
+ if (!accepted.ok) refuse2(opts, accepted, "accept-terms-refused");
18200
+ }
18201
+ const opened = await client.begin({
18202
+ componentName,
18203
+ figmaFile,
18204
+ entry: bundle.manifest.entry,
18205
+ files: bundle.files,
18206
+ report: bundle.reportText,
18207
+ confirmed: opts.confirmPublish === true
18208
+ });
18209
+ if (!opened.ok) {
18210
+ if (opened.needsConsent !== void 0) {
18211
+ fail(opts, ExitCode.ConfirmationRequired, {
18212
+ error: `publishing this design system needs an acceptance of the publishing terms (version ${opened.needsConsent.termsVersion}) first`,
18213
+ code: "publishing-terms-not-accepted",
18214
+ remediation: `Read the terms, then run \`${tendrilCommand(`publish ${opts.bundleDir} --accept-terms --confirm-publish`)}\`. The acceptance covers this design system only.`
18215
+ });
18216
+ }
18217
+ if (opened.needsConfirmation !== void 0) {
18218
+ fail(opts, ExitCode.ConfirmationRequired, {
18219
+ error: `publishing ${JSON.stringify(opened.needsConfirmation.componentName)} for the first time is a decision a person makes, not a build step`,
18220
+ code: "first-publish-unconfirmed",
18221
+ remediation: `A human runs \`${tendrilCommand(`publish ${opts.bundleDir} --confirm-publish`)}\` in their own terminal. Later publishes of this component need no flag.`
18222
+ });
18223
+ }
18224
+ refuse2(opts, opened, "publish-refused");
18225
+ }
18226
+ const uploaded = [];
18227
+ for (const object of opened.value.plan.objects) {
18228
+ const file = path52.join(bundleDir, object.relPath);
18229
+ if (!existsSync42(file)) {
18230
+ fail(opts, ExitCode.InputValidation, {
18231
+ error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
18232
+ code: "planned-file-missing",
18233
+ remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` and publish again \u2014 a publication is refused rather than going live with a hole in its evidence.`
18234
+ });
18235
+ }
18236
+ const sent = await client.upload({
18237
+ publicationId: opened.value.publicationId,
18238
+ relPath: object.relPath,
18239
+ bytes: new Uint8Array(readFileSync39(file))
18240
+ });
18241
+ if (!sent.ok) refuse2(opts, sent, "upload-refused");
18242
+ uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
18243
+ }
18244
+ const committed = await client.commit({ publicationId: opened.value.publicationId });
18245
+ if (!committed.ok) {
18246
+ if (committed.missing !== void 0 && committed.missing.length > 0) {
18247
+ fail(opts, ExitCode.General, {
18248
+ error: `this publication is missing objects the bundle declared, and a verdict beside missing evidence is worse than no page: ${committed.missing.join(", ")}`,
18249
+ code: "publication-incomplete",
18250
+ remediation: `Run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication and re-sends only what is missing.`
18251
+ });
18252
+ }
18253
+ refuse2(opts, committed, "commit-refused");
18254
+ }
18255
+ emitData(
18256
+ opts,
18257
+ {
18258
+ publicationId: committed.value.publicationId,
18259
+ url: committed.value.url,
18260
+ componentName,
18261
+ figmaFile: opened.value.figmaFile,
18262
+ rulerVersion: opened.value.rulerVersion,
18263
+ resumed: opened.value.resumed === true,
18264
+ files: uploaded
18265
+ },
18266
+ () => {
18267
+ const reused = uploaded.filter((u) => u.deduplicated).length;
18268
+ if (opened.value.resumed === true) process.stdout.write(`resumed the unfinished publish of ${componentName}
18269
+ `);
18270
+ process.stdout.write(`published ${componentName} \u2014 ${String(uploaded.length)} files`);
18271
+ process.stdout.write(reused > 0 ? ` (${String(reused)} you already had)
18272
+ ` : "\n");
18273
+ process.stdout.write(` ${committed.value.url}
18274
+ `);
18275
+ process.stdout.write(` verdict as scored by ruler ${opened.value.rulerVersion}
18276
+ `);
18277
+ }
18278
+ );
18279
+ }
18280
+ function readBundle(opts, bundleDir) {
18281
+ const manifestPath2 = path52.join(bundleDir, "component.json");
18282
+ const reportPath = path52.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
18283
+ if (!existsSync42(manifestPath2)) {
18284
+ fail(opts, ExitCode.InputValidation, {
18285
+ error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
18286
+ code: "not-a-bundle",
18287
+ remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
18288
+ });
18289
+ }
18290
+ if (!existsSync42(reportPath)) {
18291
+ fail(opts, ExitCode.InputValidation, {
18292
+ error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
18293
+ code: "bundle-not-verified",
18294
+ remediation: `Run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` first. Verification is free and needs no account; publishing without it would put a page up with no verdict on it.`
18295
+ });
18296
+ }
18297
+ const { manifest } = readBundleManifest(readFileSync39(manifestPath2, "utf8"));
18298
+ if (manifest === void 0) {
18299
+ fail(opts, ExitCode.InputValidation, {
18300
+ error: "component.json did not parse as a bundle manifest",
18301
+ code: "no-mount-contract",
18302
+ remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
18303
+ });
18304
+ }
18305
+ const reportText = readFileSync39(reportPath, "utf8");
18306
+ let report;
18307
+ try {
18308
+ report = JSON.parse(reportText);
18309
+ } catch (error) {
18310
+ fail(opts, ExitCode.InputValidation, {
18311
+ error: `${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME} is not parseable JSON: ${error.message}`,
18312
+ code: "report-unparseable",
18313
+ remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` \u2014 that file is written by the ruler and should never be edited by hand.`
18314
+ });
18315
+ }
18316
+ if (report === null || typeof report !== "object" || Array.isArray(report)) {
18317
+ fail(opts, ExitCode.InputValidation, {
18318
+ error: `${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME} parses, but a verify report is a JSON object and this is not one`,
18319
+ code: "report-not-an-object",
18320
+ remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\`.`
18321
+ });
18322
+ }
18323
+ return { manifest, report, reportText, files: bundleFiles(bundleDir) };
18324
+ }
18325
+ function httpClient(opts, origin) {
18326
+ if (origin === "") {
18327
+ fail(opts, ExitCode.InputValidation, {
18328
+ error: "no portal to publish to",
18329
+ code: "no-portal-configured",
18330
+ remediation: `Pass \`--to <url>\` or set TENDRIL_PORTAL_URL. \`${tendrilCommand(`publish ${opts.bundleDir} --dry-run`)}\` shows exactly what would be published without needing one.`
18331
+ });
18332
+ }
18333
+ if (!isSecureOrigin(origin)) {
18334
+ fail(opts, ExitCode.InputValidation, {
18335
+ error: `${origin} is not https, so a session token sent there would travel in the clear`,
18336
+ code: "portal-not-https",
18337
+ remediation: "Use the https:// address of your portal. Only 127.0.0.1 and localhost are exempt, for local development."
18338
+ });
18339
+ }
18340
+ const found = tokenFor(origin);
18341
+ if (!found.ok && found.reason === "origin-mismatch") {
18342
+ fail(opts, ExitCode.Auth, {
18343
+ error: `the session available here belongs to ${found.boundTo}, and this would publish to ${origin}`,
18344
+ code: "session-belongs-to-another-portal",
18345
+ remediation: found.from === "env" ? `TENDRIL_TOKEN is pinned to TENDRIL_PORTAL_URL (${found.boundTo}). Publish to that portal, or set both to ${origin} together \u2014 a token is a credential for one host.` : `Sign in to ${origin}. The stored session is for ${found.boundTo}, and sending it elsewhere would hand that host a live credential.`
18346
+ });
18347
+ }
18348
+ if (!found.ok) {
18349
+ fail(opts, ExitCode.Auth, {
18350
+ error: `no session for ${origin}`,
18351
+ code: "not-signed-in",
18352
+ remediation: `Run \`${tendrilCommand(`login --to ${origin}`)}\` and paste the token your portal operator gave you (or set TENDRIL_TOKEN and TENDRIL_PORTAL_URL together for this shell). Verification stays free and account-less; only publishing needs an account.`
18353
+ });
18354
+ }
18355
+ return new HttpPublishClient({ origin, token: found.token });
18356
+ }
18357
+ function refuse2(opts, sent, code) {
18358
+ const detail = sent.detail === void 0 || sent.detail.length === 0 ? "" : `: ${sent.detail.join(", ")}`;
18359
+ fail(opts, sent.status === 401 ? ExitCode.Auth : ExitCode.General, {
18360
+ error: `${sent.refusal}${detail}`,
18361
+ code,
18362
+ remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${(opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "")}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 a retry rejoins this same unfinished publication.` : `Fix what is named above and run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication rather than starting a second one.`
18363
+ });
18364
+ }
18365
+ var init_publish = __esm({
18366
+ "packages/cli/src/commands/publish.ts"() {
18367
+ "use strict";
18368
+ init_src3();
18369
+ init_src4();
18370
+ init_invocation();
18371
+ init_output();
18372
+ init_publish_client();
18373
+ }
18374
+ });
18375
+
17090
18376
  // packages/cli/src/commands/generate-route.ts
17091
18377
  var generate_route_exports = {};
17092
18378
  __export(generate_route_exports, {
@@ -17110,18 +18396,18 @@ var generate_recorded_exports = {};
17110
18396
  __export(generate_recorded_exports, {
17111
18397
  runGenerateRecorded: () => runGenerateRecorded
17112
18398
  });
17113
- import { confirm as confirm3, isCancel as isCancel3 } from "@clack/prompts";
17114
- import { existsSync as existsSync40, readFileSync as readFileSync35 } from "node:fs";
17115
- import path49 from "node:path";
18399
+ import { confirm as confirm3, isCancel as isCancel4 } from "@clack/prompts";
18400
+ import { existsSync as existsSync43, readFileSync as readFileSync40 } from "node:fs";
18401
+ import path53 from "node:path";
17116
18402
  async function runGenerateRecorded(opts) {
17117
18403
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
17118
- const outDirAbs = path49.resolve(callerCwd, opts.out);
17119
- const recordedAsPath = path49.resolve(callerCwd, opts.recorded);
18404
+ const outDirAbs = path53.resolve(callerCwd, opts.out);
18405
+ const recordedAsPath = path53.resolve(callerCwd, opts.recorded);
17120
18406
  let task;
17121
18407
  let taskName;
17122
18408
  let authoredApi;
17123
18409
  let composition;
17124
- const isSet = existsSync40(path49.join(recordedAsPath, "recording-set.json"));
18410
+ const isSet = existsSync43(path53.join(recordedAsPath, "recording-set.json"));
17125
18411
  const registry = TASKS[opts.recorded];
17126
18412
  if (registry !== void 0 && !isSet) {
17127
18413
  task = registry;
@@ -17130,7 +18416,7 @@ async function runGenerateRecorded(opts) {
17130
18416
  try {
17131
18417
  const authored = authorTaskFromSet(recordedAsPath);
17132
18418
  task = authored.task;
17133
- taskName = path49.basename(recordedAsPath);
18419
+ taskName = path53.basename(recordedAsPath);
17134
18420
  authoredApi = authored.api;
17135
18421
  const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
17136
18422
  if (roles.success) composition = roles.data;
@@ -17164,7 +18450,7 @@ async function runGenerateRecorded(opts) {
17164
18450
  warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
17165
18451
  }
17166
18452
  const missing = task.configs.filter(
17167
- (c) => !existsSync40(path49.join(task.set, c.rep, "get_screenshot.json")) || !existsSync40(path49.join(task.set, c.rep, "get_metadata.json")) || !existsSync40(path49.join(task.set, c.rep, "get_design_context.json"))
18453
+ (c) => !existsSync43(path53.join(task.set, c.rep, "get_screenshot.json")) || !existsSync43(path53.join(task.set, c.rep, "get_metadata.json")) || !existsSync43(path53.join(task.set, c.rep, "get_design_context.json"))
17168
18454
  );
17169
18455
  if (missing.length > 0) {
17170
18456
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -17234,8 +18520,8 @@ async function runGenerateRecorded(opts) {
17234
18520
  ` : `${line}
17235
18521
  `);
17236
18522
  if (opts.dryRun) {
17237
- emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path49.join(outDirAbs, taskName) }, () => {
17238
- process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path49.join(outDirAbs, taskName)})
18523
+ emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path53.join(outDirAbs, taskName) }, () => {
18524
+ process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path53.join(outDirAbs, taskName)})
17239
18525
  `);
17240
18526
  });
17241
18527
  return;
@@ -17250,7 +18536,7 @@ async function runGenerateRecorded(opts) {
17250
18536
  });
17251
18537
  }
17252
18538
  const accepted = await confirm3({ message: `Proceed? (cap $${opts.capUsd.toFixed(2)}, ${opts.maxIterations} iterations max)` });
17253
- if (isCancel3(accepted) || accepted !== true) {
18539
+ if (isCancel4(accepted) || accepted !== true) {
17254
18540
  fail(opts, ExitCode.ConfirmationRequired, {
17255
18541
  error: "Run was not confirmed.",
17256
18542
  code: "CONFIRMATION_DECLINED",
@@ -17258,10 +18544,10 @@ async function runGenerateRecorded(opts) {
17258
18544
  });
17259
18545
  }
17260
18546
  }
17261
- const bundleDir = path49.join(outDirAbs, taskName);
17262
- if (existsSync40(path49.join(bundleDir, "component.json"))) {
18547
+ const bundleDir = path53.join(outDirAbs, taskName);
18548
+ if (existsSync43(path53.join(bundleDir, "component.json"))) {
17263
18549
  try {
17264
- const prior = readBundleManifest(readFileSync35(path49.join(bundleDir, "component.json"), "utf8")).manifest;
18550
+ const prior = readBundleManifest(readFileSync40(path53.join(bundleDir, "component.json"), "utf8")).manifest;
17265
18551
  if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
17266
18552
  fail(opts, ExitCode.InputValidation, {
17267
18553
  error: `${bundleDir} already holds a bundle bound to recording set "${prior.provenance.recordingSet.path}" \u2014 generating here against a different set would silently rewrite its verification identity`,
@@ -17428,7 +18714,7 @@ init_invocation();
17428
18714
  init_output();
17429
18715
  import { intro, isCancel, outro, password } from "@clack/prompts";
17430
18716
  import fs from "node:fs";
17431
- import path27 from "node:path";
18717
+ import path29 from "node:path";
17432
18718
  var INIT_DESCRIPTION = {
17433
18719
  name: "init",
17434
18720
  summary: "Configure the OpenRouter credential in .env, and optionally a Figma token (idempotent).",
@@ -17470,7 +18756,7 @@ async function runInit(flags) {
17470
18756
  printDescription(INIT_DESCRIPTION);
17471
18757
  return;
17472
18758
  }
17473
- const envPath = path27.resolve(process.cwd(), ".env");
18759
+ const envPath = path29.resolve(process.cwd(), ".env");
17474
18760
  const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
17475
18761
  let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
17476
18762
  let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
@@ -17491,7 +18777,7 @@ async function runInit(flags) {
17491
18777
  if (openrouterKey) next.set(ENV_KEYS.openrouter, openrouterKey);
17492
18778
  if (figmaToken) next.set(ENV_KEYS.figma, figmaToken);
17493
18779
  const changed = [...next].some(([key, value]) => existing.get(key) !== value);
17494
- const gitignorePath = path27.resolve(process.cwd(), ".gitignore");
18780
+ const gitignorePath = path29.resolve(process.cwd(), ".gitignore");
17495
18781
  const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
17496
18782
  const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
17497
18783
  if (flags.dryRun) {
@@ -17547,14 +18833,14 @@ init_invocation();
17547
18833
  init_output();
17548
18834
  init_entitlement();
17549
18835
  import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
17550
- import { readFileSync as readFileSync18, readdirSync as readdirSync8, existsSync as existsSync22 } from "node:fs";
18836
+ import { readFileSync as readFileSync21, readdirSync as readdirSync9, existsSync as existsSync23 } from "node:fs";
17551
18837
 
17552
18838
  // packages/cli/src/pipeline.ts
17553
18839
  init_src2();
17554
18840
  init_src5();
17555
18841
  init_src4();
17556
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
17557
- import path28 from "node:path";
18842
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "node:fs";
18843
+ import path30 from "node:path";
17558
18844
 
17559
18845
  // packages/cli/src/assets-module.ts
17560
18846
  init_src();
@@ -17890,8 +19176,8 @@ async function runGenerationPipeline(input) {
17890
19176
  });
17891
19177
  const written = [];
17892
19178
  if (!input.dryRun) {
17893
- const dir = path28.resolve(input.outDir, semantics.componentName);
17894
- mkdirSync5(dir, { recursive: true });
19179
+ const dir = path30.resolve(input.outDir, semantics.componentName);
19180
+ mkdirSync6(dir, { recursive: true });
17895
19181
  const files = {
17896
19182
  // Bundle-local tokens: THE emission the component's CSS resolves
17897
19183
  // against — the same artifact the verify harness injects. A preview
@@ -17914,14 +19200,14 @@ async function runGenerationPipeline(input) {
17914
19200
  `
17915
19201
  };
17916
19202
  for (const [name, content] of Object.entries(files)) {
17917
- const filePath = path28.join(dir, name);
17918
- writeFileSync8(filePath, content);
19203
+ const filePath = path30.join(dir, name);
19204
+ writeFileSync9(filePath, content);
17919
19205
  written.push(filePath);
17920
19206
  }
17921
19207
  for (const artifact of emitTokenArtifacts(input.mapping)) {
17922
- const filePath = path28.resolve(input.outDir, artifact.path);
17923
- mkdirSync5(path28.dirname(filePath), { recursive: true });
17924
- writeFileSync8(filePath, artifact.content);
19208
+ const filePath = path30.resolve(input.outDir, artifact.path);
19209
+ mkdirSync6(path30.dirname(filePath), { recursive: true });
19210
+ writeFileSync9(filePath, artifact.content);
17925
19211
  written.push(filePath);
17926
19212
  }
17927
19213
  }
@@ -17979,7 +19265,7 @@ var GENERATE_DESCRIPTION = {
17979
19265
  function resolveProvidedSource(flags, contextFile) {
17980
19266
  let raw;
17981
19267
  try {
17982
- raw = readFileSync18(contextFile, "utf8");
19268
+ raw = readFileSync21(contextFile, "utf8");
17983
19269
  } catch {
17984
19270
  fail(flags, ExitCode.InputValidation, {
17985
19271
  error: `Cannot read context file "${contextFile}".`,
@@ -18099,11 +19385,11 @@ token mapping (${mapping.flat.length} variables):
18099
19385
  let initialCode;
18100
19386
  let initialSemantics;
18101
19387
  try {
18102
- if (existsSync22(flags.out)) {
18103
- for (const entry of readdirSync8(flags.out)) {
19388
+ if (existsSync23(flags.out)) {
19389
+ for (const entry of readdirSync9(flags.out)) {
18104
19390
  const cjPath = `${flags.out}/${entry}/component.json`;
18105
- if (!existsSync22(cjPath)) continue;
18106
- const cj = JSON.parse(readFileSync18(cjPath, "utf8"));
19391
+ if (!existsSync23(cjPath)) continue;
19392
+ const cj = JSON.parse(readFileSync21(cjPath, "utf8"));
18107
19393
  if (cj.name !== void 0 && Array.isArray(cj.props)) {
18108
19394
  previousApi = JSON.stringify({
18109
19395
  componentName: cj.name,
@@ -18111,14 +19397,14 @@ token mapping (${mapping.flat.length} variables):
18111
19397
  });
18112
19398
  const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
18113
19399
  const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
18114
- if (flags.refine && existsSync22(tsxPath) && existsSync22(cssPath)) {
19400
+ if (flags.refine && existsSync23(tsxPath) && existsSync23(cssPath)) {
18115
19401
  initialCode = {
18116
- tsx: readFileSync18(tsxPath, "utf8"),
18117
- css: readFileSync18(cssPath, "utf8")
19402
+ tsx: readFileSync21(tsxPath, "utf8"),
19403
+ css: readFileSync21(cssPath, "utf8")
18118
19404
  };
18119
19405
  const semPath = `${flags.out}/${entry}/semantics.json`;
18120
- if (existsSync22(semPath)) {
18121
- initialSemantics = JSON.parse(readFileSync18(semPath, "utf8"));
19406
+ if (existsSync23(semPath)) {
19407
+ initialSemantics = JSON.parse(readFileSync21(semPath, "utf8"));
18122
19408
  }
18123
19409
  }
18124
19410
  break;
@@ -18513,6 +19799,50 @@ function buildProgram() {
18513
19799
  ...local["maxArea"] !== void 0 ? { maxArea: Number(local["maxArea"]) } : {}
18514
19800
  });
18515
19801
  });
19802
+ program.command("login").description("Sign this machine in to the Tendril portal (approve in your browser), so `publish` and `share` can reach it. Verification never needs this.").option("--to <url>", "the portal to sign in to (defaults to the hosted portal; or set TENDRIL_PORTAL_URL)").option("--paste", "paste a token minted on the portal's Connect page instead of approving in the browser").option("--device-start", "agents: start the browser approval and return the link and code immediately").option("--device-wait", "agents: wait for the browser approval started by --device-start").action(async (_o, cmd) => {
19803
+ const flags = globalFlags(cmd.parent);
19804
+ const local = cmd.opts();
19805
+ const { runLogin: runLogin2 } = await Promise.resolve().then(() => (init_login(), login_exports));
19806
+ await runLogin2({
19807
+ ...flags,
19808
+ ...local["to"] !== void 0 ? { to: local["to"] } : {},
19809
+ ...local["paste"] !== void 0 ? { paste: local["paste"] } : {},
19810
+ ...local["deviceStart"] !== void 0 ? { deviceStart: local["deviceStart"] } : {},
19811
+ ...local["deviceWait"] !== void 0 ? { deviceWait: local["deviceWait"] } : {}
19812
+ });
19813
+ });
19814
+ program.command("logout").description("Forget this machine's stored portal session. Does not end the session itself.").action(async (_o, cmd) => {
19815
+ const flags = globalFlags(cmd.parent);
19816
+ const { runLogout: runLogout2 } = await Promise.resolve().then(() => (init_login(), login_exports));
19817
+ runLogout2(flags);
19818
+ });
19819
+ program.command("share").description("Create a link that opens one publication's verdict and evidence for someone with no account. Anyone holding the link can read it.").argument("<publicationId>", "the publication to share, as `publish` printed it").option("--to <url>", "the portal (or set TENDRIL_PORTAL_URL)").option("--expires <days|timestamp>", "a number of days, or an ISO-8601 UTC timestamp; omit for a link that does not expire").option("--recipient <email>", "recorded so you can see who a link was for; never used to authenticate").option("--list", "show the links outstanding for this publication").option("--revoke <shareLinkId>", "stop a link working").action(async (publicationId, _o, cmd) => {
19820
+ const flags = globalFlags(cmd.parent);
19821
+ const local = cmd.opts();
19822
+ const { runShare: runShare2 } = await Promise.resolve().then(() => (init_share(), share_exports));
19823
+ await runShare2({
19824
+ ...flags,
19825
+ publicationId,
19826
+ ...local["to"] !== void 0 ? { to: local["to"] } : {},
19827
+ ...local["expires"] !== void 0 ? { expires: String(local["expires"]) } : {},
19828
+ ...local["recipient"] !== void 0 ? { recipient: local["recipient"] } : {},
19829
+ ...local["list"] !== void 0 ? { list: local["list"] } : {},
19830
+ ...local["revoke"] !== void 0 ? { revoke: local["revoke"] } : {}
19831
+ });
19832
+ });
19833
+ program.command("publish").description("Put a verified bundle and its evidence on a page, at a URL you can send to someone. Refuses a run the ruler declined.").argument("<bundleDir>", "bundle directory that has already been verified").option("--to <url>", "the portal to publish to (or set TENDRIL_PORTAL_URL)").option("--name <name>", "library label for this component (default: the bundle manifest's own name)").option("--accept-terms", "accept the current publishing terms for this design system as part of this run").option("--confirm-publish", "you, a person, saying this component may be published \u2014 required the first time, and refused without an interactive terminal").action(async (bundleDir, _o, cmd) => {
19834
+ const flags = globalFlags(cmd.parent);
19835
+ const local = cmd.opts();
19836
+ const { runPublish: runPublish2 } = await Promise.resolve().then(() => (init_publish(), publish_exports));
19837
+ await runPublish2({
19838
+ ...flags,
19839
+ bundleDir,
19840
+ ...local["to"] !== void 0 ? { to: local["to"] } : {},
19841
+ ...local["name"] !== void 0 ? { name: local["name"] } : {},
19842
+ ...local["acceptTerms"] !== void 0 ? { acceptTerms: local["acceptTerms"] } : {},
19843
+ ...local["confirmPublish"] !== void 0 ? { confirmPublish: local["confirmPublish"] } : {}
19844
+ });
19845
+ });
18516
19846
  program.command("verify").description("Recompute verification for a generated bundle against its recording set (per-config status, behaviors, honest exit codes).").argument("<bundleDir>", "bundle directory to verify").option("--task <name>", "legacy task adapter for bundles WITHOUT component.json (bundle v1 carries its own prop manifest)").option("--set <dir>", "recording set directory (overrides the bundle's provenance path)").option("--bar <bar>", "target bar: pass (0.95) or cert (0.97)", "pass").option("--library <dir>", "workspace root holding confirmed partners generated bundles (ADR-013 composed pins; default: current directory)").option("--profile <file>", "a `tendril profile` artifact; reports whether the bundle followed your codebase conventions (never gates the verdict)").action(async (bundleDir, _opts, cmd) => {
18517
19847
  const flags = globalFlags(cmd);
18518
19848
  const local = cmd.opts();