@tendrilapp/cli 0.1.41 → 0.1.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/tendril.js +652 -516
  2. package/package.json +1 -1
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(path54) {
1984
- return `--${path54.join("-")}`;
1983
+ function tokenPathToCssVar(path55) {
1984
+ return `--${path55.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 path54 = variableNameToPath(variable.name);
2029
- if (path54.length === 0) {
2028
+ const path55 = variableNameToPath(variable.name);
2029
+ if (path55.length === 0) {
2030
2030
  throw new DtcgMappingError("empty-name", `Figma variable ${variable.id} has an empty name`);
2031
2031
  }
2032
- return { variable, path: path54 };
2032
+ return { variable, path: path55 };
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: path54 } of entries) {
2053
+ for (const { variable, path: path55 } of entries) {
2054
2054
  const token = toDtcgToken(variable, defaultMode);
2055
2055
  let group = tokens;
2056
- for (const segment of path54.slice(0, -1)) {
2056
+ for (const segment of path55.slice(0, -1)) {
2057
2057
  const existing = group[segment];
2058
2058
  group = existing ?? (group[segment] = {});
2059
2059
  }
2060
- const leaf = path54[path54.length - 1];
2060
+ const leaf = path55[path55.length - 1];
2061
2061
  if (group[leaf] !== void 0) {
2062
- throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path54.join(".")}" (variable ${variable.id})`);
2062
+ throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path55.join(".")}" (variable ${variable.id})`);
2063
2063
  }
2064
2064
  group[leaf] = token;
2065
2065
  flat.push({
2066
- path: path54.join("."),
2067
- cssVar: tokenPathToCssVar(path54),
2066
+ path: path55.join("."),
2067
+ cssVar: tokenPathToCssVar(path55),
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 path54 = ctx.pathById.get(id);
2257
- if (path54 === void 0) ctx.unresolved.add(id);
2258
- return path54;
2256
+ const path55 = ctx.pathById.get(id);
2257
+ if (path55 === void 0) ctx.unresolved.add(id);
2258
+ return path55;
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 path54 = resolveBinding(ctx, id);
2294
- if (path54 !== void 0) tokens.add(path54);
2293
+ const path55 = resolveBinding(ctx, id);
2294
+ if (path55 !== void 0) tokens.add(path55);
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 path54 = resolveBinding(ctx, radiusId);
2303
- if (path54 !== void 0) tokens.add(path54);
2302
+ const path55 = resolveBinding(ctx, radiusId);
2303
+ if (path55 !== void 0) tokens.add(path55);
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 path54 = resolveBinding(ctx, gapId);
2314
- if (path54 !== void 0) {
2315
- layout.gap = path54;
2316
- tokens.add(path54);
2313
+ const path55 = resolveBinding(ctx, gapId);
2314
+ if (path55 !== void 0) {
2315
+ layout.gap = path55;
2316
+ tokens.add(path55);
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 path54 = resolveBinding(ctx, id);
2326
- if (path54 !== void 0) {
2327
- paddingPaths.push(path54);
2328
- tokens.add(path54);
2325
+ const path55 = resolveBinding(ctx, id);
2326
+ if (path55 !== void 0) {
2327
+ paddingPaths.push(path55);
2328
+ tokens.add(path55);
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` });
@@ -4830,6 +4830,65 @@ var init_font_faces = __esm({
4830
4830
  }
4831
4831
  });
4832
4832
 
4833
+ // packages/verify/src/mount-error.ts
4834
+ function describeMountError(err) {
4835
+ if (!(err instanceof Error)) return String(err);
4836
+ const lines = err.message.split("\n").map((l) => l.trim()).filter((l) => l !== "" && l !== "Call log:" && l !== "=".repeat(l.length));
4837
+ const headline = lines[0] ?? "unknown error";
4838
+ if (lines.length <= 1) return headline;
4839
+ const counts = /* @__PURE__ */ new Map();
4840
+ for (const raw of lines.slice(1)) {
4841
+ const line = raw.replace(/^[-\s]+/, "");
4842
+ counts.set(line, (counts.get(line) ?? 0) + 1);
4843
+ }
4844
+ const distinct = [...counts.entries()];
4845
+ const shown = distinct.slice(0, MAX_DISTINCT_LOG_LINES);
4846
+ const condensed = shown.map(([line, n]) => n > 1 ? `${line} \xD7${n}` : line).join("; ");
4847
+ const dropped = distinct.length - shown.length;
4848
+ const tail = dropped > 0 ? `; (+${dropped} more distinct lines)` : "";
4849
+ const out = `${headline} call log: ${condensed}${tail}`;
4850
+ return out.length > MAX_DETAIL_CHARS ? `${out.slice(0, MAX_DETAIL_CHARS - 1)}\u2026` : out;
4851
+ }
4852
+ async function hoverWithBudget(page, selector, budgetMs) {
4853
+ const started = Date.now();
4854
+ try {
4855
+ await page.hover(selector, { timeout: budgetMs });
4856
+ return Date.now() - started;
4857
+ } catch (err) {
4858
+ const elapsed = Date.now() - started;
4859
+ if (err instanceof Error && /Timeout \d+ms exceeded/.test(err.message)) {
4860
+ const callLog = err.message.split("\n").slice(1).join("\n");
4861
+ throw new Error(
4862
+ `hover on ${selector} blew its ${String(budgetMs)} ms budget (gave up after ${String(elapsed)} ms) \u2014 a blown DEADLINE, not proof the element cannot be hovered; the call log names the phase that never completed (raise the budget with --hover-timeout to diagnose a slow machine; the report records any non-default budget)
4863
+ ${callLog}`,
4864
+ { cause: err }
4865
+ );
4866
+ }
4867
+ throw err;
4868
+ }
4869
+ }
4870
+ function capturePageConsole(page) {
4871
+ const messages = [];
4872
+ page.on("console", (m) => {
4873
+ messages.push(`${m.type()}: ${m.text().slice(0, 200)}`);
4874
+ if (messages.length > 8) messages.shift();
4875
+ });
4876
+ page.on("pageerror", (e) => {
4877
+ messages.push(`pageerror: ${String(e.message ?? e).slice(0, 200)}`);
4878
+ if (messages.length > 8) messages.shift();
4879
+ });
4880
+ return () => messages.join(" | ");
4881
+ }
4882
+ var DEFAULT_HOVER_BUDGET_MS, MAX_DISTINCT_LOG_LINES, MAX_DETAIL_CHARS;
4883
+ var init_mount_error = __esm({
4884
+ "packages/verify/src/mount-error.ts"() {
4885
+ "use strict";
4886
+ DEFAULT_HOVER_BUDGET_MS = 2e3;
4887
+ MAX_DISTINCT_LOG_LINES = 16;
4888
+ MAX_DETAIL_CHARS = 900;
4889
+ }
4890
+ });
4891
+
4833
4892
  // packages/verify/src/admission.ts
4834
4893
  import { readFileSync as readFileSync9, readdirSync as readdirSync4, existsSync as existsSync11, writeFileSync as writeFileSync4 } from "node:fs";
4835
4894
  import path15 from "node:path";
@@ -4855,6 +4914,7 @@ var init_admission = __esm({
4855
4914
  init_font_faces();
4856
4915
  init_browser();
4857
4916
  init_font_resolve();
4917
+ init_mount_error();
4858
4918
  init_mount_limits();
4859
4919
  FONT_WEIGHTS = fontWeightsByFamily();
4860
4920
  }
@@ -5166,7 +5226,7 @@ async function settle(page, ms = 150) {
5166
5226
  await page.waitForTimeout(ms);
5167
5227
  await freezeAnimations(page);
5168
5228
  }
5169
- async function runSteps(page, spec, renderPose) {
5229
+ async function runSteps(page, spec, renderPose, hoverBudgetMs = DEFAULT_HOVER_BUDGET_MS) {
5170
5230
  for (const step of spec.steps) {
5171
5231
  try {
5172
5232
  if ("commitMatchesPose" in step) {
@@ -5215,7 +5275,7 @@ async function runSteps(page, spec, renderPose) {
5215
5275
  await page.click(`#root ${step.click}`, { timeout: 2e3 });
5216
5276
  await page.waitForTimeout(150);
5217
5277
  } else if ("hover" in step) {
5218
- await page.hover(`#root ${step.hover}`, { timeout: 2e3 });
5278
+ await hoverWithBudget(page, `#root ${step.hover}`, hoverBudgetMs);
5219
5279
  await page.waitForTimeout(150);
5220
5280
  } else if ("key" in step) {
5221
5281
  await page.keyboard.press(step.key);
@@ -5447,7 +5507,7 @@ async function runSteps(page, spec, renderPose) {
5447
5507
  }
5448
5508
  } else if ("hoverChangesPixels" in step) {
5449
5509
  const before = await page.screenshot();
5450
- await page.hover(`#root ${step.hoverChangesPixels}`, { timeout: 2e3 });
5510
+ await hoverWithBudget(page, `#root ${step.hoverChangesPixels}`, hoverBudgetMs);
5451
5511
  await page.waitForTimeout(200);
5452
5512
  const after = await page.screenshot();
5453
5513
  await page.mouse.move(0, 0);
@@ -5608,11 +5668,11 @@ body{margin:0;padding:20px}
5608
5668
  const why = pageErrors.length > 0 ? ` (runtime error: ${pageErrors[0]})` : "";
5609
5669
  return { id: spec.id, pass: false, detail: `${cfg.component} rendered nothing${why}` };
5610
5670
  }
5611
- return await runSteps(page, spec, renderPose);
5671
+ return await runSteps(page, spec, renderPose, opts.hoverBudgetMs ?? DEFAULT_HOVER_BUDGET_MS);
5612
5672
  } finally {
5613
5673
  await page.close();
5614
5674
  }
5615
- })().catch((err) => ({ id: spec.id, pass: false, detail: `mount failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` }));
5675
+ })().catch((err) => ({ id: spec.id, pass: false, detail: `mount failed: ${describeMountError(err)}` }));
5616
5676
  const result = await raceMountDeadline(
5617
5677
  work,
5618
5678
  deadlineMs,
@@ -5647,7 +5707,7 @@ body{margin:0;padding:20px}
5647
5707
  } finally {
5648
5708
  await page.close();
5649
5709
  }
5650
- })().catch((err) => [{ id: "prelude:root", pass: false, detail: `mount failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` }]);
5710
+ })().catch((err) => [{ id: "prelude:root", pass: false, detail: `mount failed: ${describeMountError(err)}` }]);
5651
5711
  results.push(
5652
5712
  ...await raceMountDeadline(work, deadlineMs, () => [
5653
5713
  { id: "prelude:root", pass: false, detail: `mount deadline exceeded (${deadlineMs}ms) \u2014 hostile or hung candidate` }
@@ -5671,6 +5731,7 @@ var init_behavior = __esm({
5671
5731
  init_candidate_css();
5672
5732
  init_font_faces();
5673
5733
  init_image_diff();
5734
+ init_mount_error();
5674
5735
  init_mount_limits();
5675
5736
  init_tasks();
5676
5737
  ANIMATED_PROBE = `(() => {
@@ -6178,33 +6239,34 @@ function classifyBundleSurface(files, opts) {
6178
6239
  const excluded = [];
6179
6240
  const unknown = [];
6180
6241
  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;
6242
+ const path55 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
6243
+ const inEvidence = path55.startsWith(`${EVIDENCE_DIR}/`);
6244
+ const name = inEvidence ? path55.slice(EVIDENCE_DIR.length + 1) : path55;
6184
6245
  if (name.includes("/")) {
6185
- unknown.push(path54);
6246
+ unknown.push(path55);
6186
6247
  continue;
6187
6248
  }
6188
6249
  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" });
6250
+ if (name === "verify-report.json") published.push({ path: path55, role: "verify-report" });
6251
+ else if (name === "diff-legend.txt") published.push({ path: path55, role: "diff-legend" });
6252
+ else if (name === "inspect.html") published.push({ path: path55, role: "inspect-sheet" });
6253
+ else if (/-mount-failure\.png$/.test(name)) excluded.push({ path: path55, reason: "harness failure diagnostic (regenerated every verify run, never published)" });
6192
6254
  else {
6193
6255
  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);
6256
+ if (hit !== void 0) published.push({ path: path55, role: hit.role });
6257
+ else unknown.push(path55);
6196
6258
  }
6197
6259
  continue;
6198
6260
  }
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" });
6261
+ if (name === opts.entry) published.push({ path: path55, role: "entry" });
6262
+ else if (name === "styles.css") published.push({ path: path55, role: "styles" });
6263
+ else if (name === "tokens.css") published.push({ path: path55, role: "tokens" });
6264
+ else if (name === "fonts.css") published.push({ path: path55, role: "fonts" });
6265
+ else if (name === "component.json") published.push({ path: path55, role: "manifest" });
6204
6266
  else {
6205
6267
  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);
6268
+ if (skip !== void 0) excluded.push({ path: path55, reason: skip.reason });
6269
+ else unknown.push(path55);
6208
6270
  }
6209
6271
  }
6210
6272
  const roles = new Set(published.map((p) => p.role));
@@ -6214,8 +6276,8 @@ function missingInspectCrops(sheetText, publishedPaths) {
6214
6276
  const held = new Set(publishedPaths);
6215
6277
  const missing = /* @__PURE__ */ new Set();
6216
6278
  for (const [, name] of sheetText.matchAll(/src="\.\/([^"]+)"/g)) {
6217
- const path54 = `${EVIDENCE_DIR}/${name}`;
6218
- if (!held.has(path54)) missing.add(path54);
6279
+ const path55 = `${EVIDENCE_DIR}/${name}`;
6280
+ if (!held.has(path55)) missing.add(path55);
6219
6281
  }
6220
6282
  return [...missing].sort();
6221
6283
  }
@@ -6323,10 +6385,10 @@ function readScoredFiles(report) {
6323
6385
  const entries = Object.entries(value);
6324
6386
  if (entries.length === 0) return void 0;
6325
6387
  const out = {};
6326
- for (const [path54, digest] of entries) {
6327
- if (path54 === "" || path54.startsWith("/") || path54.includes("..")) return void 0;
6388
+ for (const [path55, digest] of entries) {
6389
+ if (path55 === "" || path55.startsWith("/") || path55.includes("..")) return void 0;
6328
6390
  if (!isSetHash(digest)) return void 0;
6329
- out[path54] = digest;
6391
+ out[path55] = digest;
6330
6392
  }
6331
6393
  return out;
6332
6394
  }
@@ -6334,11 +6396,11 @@ function compareScoredFiles(recorded, actual) {
6334
6396
  const missing = [];
6335
6397
  const unscored = [];
6336
6398
  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);
6399
+ for (const [path55, digest] of Object.entries(recorded)) {
6400
+ if (!(path55 in actual)) missing.push(path55);
6401
+ else if (actual[path55] !== digest) changed.push(path55);
6340
6402
  }
6341
- for (const path54 of Object.keys(actual)) if (!(path54 in recorded)) unscored.push(path54);
6403
+ for (const path55 of Object.keys(actual)) if (!(path55 in recorded)) unscored.push(path55);
6342
6404
  return { missing: missing.sort(), unscored: unscored.sort(), changed: changed.sort() };
6343
6405
  }
6344
6406
  function scoredRecordingSetHash(report) {
@@ -7317,7 +7379,7 @@ body{margin:0;background:rgb(${backdrop.join(",")})}
7317
7379
  inkRecall: 0,
7318
7380
  exact: { similarity: 0, inkRecall: 0 },
7319
7381
  pass: false,
7320
- error: `mount failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`
7382
+ error: `mount failed: ${describeMountError(err)}`
7321
7383
  })
7322
7384
  );
7323
7385
  const deadlineMs = timeoutMs + 1e4;
@@ -7361,6 +7423,7 @@ var init_bundle_score = __esm({
7361
7423
  init_effect_geometry();
7362
7424
  init_image_diff();
7363
7425
  init_font_faces();
7426
+ init_mount_error();
7364
7427
  init_mount_limits();
7365
7428
  init_tasks();
7366
7429
  BAR = { sim: 0.95, ink: 0.95 };
@@ -7401,6 +7464,8 @@ var init_prelude = __esm({
7401
7464
  });
7402
7465
 
7403
7466
  // packages/verify/src/parity.ts
7467
+ import { writeFileSync as writeFileSync7 } from "node:fs";
7468
+ import path22 from "node:path";
7404
7469
  import { chromium as chromium6 } from "playwright-core";
7405
7470
  function getFontFaces3() {
7406
7471
  _fontFaces3 ??= fontFaceCss();
@@ -7445,6 +7510,8 @@ async function checkHoverParity(task, bundleDir, authority, opts = {}) {
7445
7510
  const rootCss = box === void 0 ? "#root{position:static;display:inline-block}" : `#root{width:${box.w}px;height:${box.h}px;position:absolute;left:20px;top:20px}
7446
7511
  #root > *{min-width:${box.w}px;min-height:${box.h}px}`;
7447
7512
  const viewport = box === void 0 ? { width: 900, height: 700 } : { width: box.w + 48, height: box.h + 48 };
7513
+ let consoleTail = "";
7514
+ let failureShot;
7448
7515
  const shoot = async (component, props, realHover) => {
7449
7516
  const html = `<!doctype html><html><head><meta charset="utf-8"><style>
7450
7517
  ${getFontFaces3()}
@@ -7456,13 +7523,23 @@ ${rootCss}
7456
7523
  const page = await browser.newPage({ viewport });
7457
7524
  try {
7458
7525
  page.setDefaultTimeout(timeoutMs);
7526
+ const consoleOf = capturePageConsole(page);
7459
7527
  await page.route("**/*", (route) => route.request().url().startsWith("data:") ? route.continue() : route.abort());
7460
7528
  await page.setContent(html, { waitUntil: "load" });
7461
7529
  await page.waitForTimeout(250);
7462
7530
  const rendered = await page.evaluate("document.querySelector('#root > *') !== null");
7463
7531
  if (rendered !== true) return { error: `${cfg.component} rendered nothing` };
7464
7532
  if (realHover) {
7465
- await page.hover("#root > *", { timeout: 2e3 });
7533
+ try {
7534
+ await hoverWithBudget(page, "#root > *", opts.hoverBudgetMs ?? DEFAULT_HOVER_BUDGET_MS);
7535
+ } catch (err) {
7536
+ consoleTail = consoleOf();
7537
+ try {
7538
+ failureShot = await page.screenshot();
7539
+ } catch {
7540
+ }
7541
+ throw err;
7542
+ }
7466
7543
  await page.waitForTimeout(400);
7467
7544
  } else {
7468
7545
  await page.waitForTimeout(400);
@@ -7501,7 +7578,19 @@ ${rootCss}
7501
7578
  };
7502
7579
  }
7503
7580
  return { id: `parity:${cfg.rep}`, pass: true };
7504
- })().catch((err) => ({ id: `parity:${cfg.rep}`, pass: false, detail: `mount failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` }));
7581
+ })().catch((err) => {
7582
+ let detail = `mount failed: ${describeMountError(err)}`;
7583
+ if (consoleTail !== "") detail += ` | browser console: ${consoleTail}`;
7584
+ if (failureShot !== void 0 && opts.failureShotDir !== void 0) {
7585
+ const name = `parity-${cfg.rep}-mount-failure.png`;
7586
+ try {
7587
+ writeFileSync7(path22.join(opts.failureShotDir, name), failureShot);
7588
+ detail += ` | failure screenshot: ${name}`;
7589
+ } catch {
7590
+ }
7591
+ }
7592
+ return { id: `parity:${cfg.rep}`, pass: false, detail };
7593
+ });
7505
7594
  const result = await raceMountDeadline(
7506
7595
  work,
7507
7596
  deadlineMs,
@@ -7531,6 +7620,7 @@ var init_parity = __esm({
7531
7620
  init_candidate_css();
7532
7621
  init_behavior();
7533
7622
  init_font_faces();
7623
+ init_mount_error();
7534
7624
  init_mount_limits();
7535
7625
  init_bundle_score();
7536
7626
  }
@@ -7538,8 +7628,8 @@ var init_parity = __esm({
7538
7628
 
7539
7629
  // packages/verify/src/composition.ts
7540
7630
  import { createRequire as createRequire2 } from "node:module";
7541
- import { existsSync as existsSync16 } from "node:fs";
7542
- import path22 from "node:path";
7631
+ import { existsSync as existsSync17 } from "node:fs";
7632
+ import path23 from "node:path";
7543
7633
  import { build as build6 } from "esbuild";
7544
7634
  import { chromium as chromium7 } from "playwright-core";
7545
7635
  function getFontFaces4() {
@@ -7547,21 +7637,21 @@ function getFontFaces4() {
7547
7637
  return _fontFaces4;
7548
7638
  }
7549
7639
  async function compileInstrumentedMount(task, bundleDir, composedParts = []) {
7550
- const entryTsx = path22.join(bundleDir, task.entry);
7551
- if (!existsSync16(entryTsx)) return { error: `${task.entry} missing` };
7552
- const requireFromVerify = createRequire2(path22.join(VERIFY_PKG_DIR, "package.json"));
7640
+ const entryTsx = path23.join(bundleDir, task.entry);
7641
+ if (!existsSync17(entryTsx)) return { error: `${task.entry} missing` };
7642
+ const requireFromVerify = createRequire2(path23.join(VERIFY_PKG_DIR, "package.json"));
7553
7643
  let realJsxPath;
7554
7644
  try {
7555
7645
  realJsxPath = requireFromVerify.resolve("react/jsx-runtime");
7556
7646
  } catch (err) {
7557
- return { error: `cannot resolve react/jsx-runtime: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` };
7647
+ return { error: `cannot resolve react/jsx-runtime: ${describeMountError(err)}` };
7558
7648
  }
7559
7649
  const mountSrc = `
7560
7650
  import { createElement } from "react";
7561
7651
  import { createRoot } from "react-dom/client";
7562
7652
  import { __registerParts } from "react/jsx-runtime";
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")}
7653
+ import * as B from ${JSON.stringify(path23.resolve(entryTsx))};
7654
+ ${composedParts.map((p, i) => `import * as CP${i} from ${JSON.stringify(path23.resolve(bundleDir, p.modulePath))};`).join("\n")}
7565
7655
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; partComponents: string[] } }).__cfg;
7566
7656
  const pairs: Array<[unknown, string]> = [];
7567
7657
  for (const name of cfg.partComponents) {
@@ -7600,7 +7690,7 @@ if (root && Main) createRoot(root).render(createElement(Main, cfg.props));
7600
7690
  });
7601
7691
  return bundle.outputFiles[0]?.text ?? "";
7602
7692
  } catch (err) {
7603
- return { error: `does not compile: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` };
7693
+ return { error: `does not compile: ${describeMountError(err)}` };
7604
7694
  }
7605
7695
  }
7606
7696
  function expectedParts(task, roles, mainSlug) {
@@ -7617,7 +7707,7 @@ function expectedParts(task, roles, mainSlug) {
7617
7707
  }
7618
7708
  function interiorRegions(setDir, roles) {
7619
7709
  const mains = roles.main;
7620
- const withInterior = mains.filter((m) => existsSync16(path22.join(setDir, m, "get_metadata_interior.json")));
7710
+ const withInterior = mains.filter((m) => existsSync17(path23.join(setDir, m, "get_metadata_interior.json")));
7621
7711
  if (withInterior.length === 0) {
7622
7712
  return { unavailable: "no interior geometry recorded for any main (pre-obligation set) \u2014 re-record with `tendril record` to enable crop checks" };
7623
7713
  }
@@ -7681,7 +7771,7 @@ async function checkCropComposition(task, bundleDir, regions, opts = {}) {
7681
7771
  }
7682
7772
  return out;
7683
7773
  })().catch(
7684
- (err) => mainRegions.map((r) => ({ id: cropId(r), pass: false, similarity: 0, inkRecall: 0, detail: `mount failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` }))
7774
+ (err) => mainRegions.map((r) => ({ id: cropId(r), pass: false, similarity: 0, inkRecall: 0, detail: `mount failed: ${describeMountError(err)}` }))
7685
7775
  );
7686
7776
  const mainResults = await raceMountDeadline(
7687
7777
  work,
@@ -7788,7 +7878,7 @@ body{margin:0;padding:20px}
7788
7878
  } finally {
7789
7879
  await page.close();
7790
7880
  }
7791
- })().catch((err) => ({ id: `composition:${mainSlug}`, pass: false, detail: `mount failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` }));
7881
+ })().catch((err) => ({ id: `composition:${mainSlug}`, pass: false, detail: `mount failed: ${describeMountError(err)}` }));
7792
7882
  const result = await raceMountDeadline(
7793
7883
  work,
7794
7884
  deadlineMs,
@@ -7849,7 +7939,7 @@ body{margin:0;padding:20px}
7849
7939
  } finally {
7850
7940
  await page.close();
7851
7941
  }
7852
- })().catch((err) => ({ id: `composition:${exp.pairKey}:renders`, pass: false, detail: `mount failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` }));
7942
+ })().catch((err) => ({ id: `composition:${exp.pairKey}:renders`, pass: false, detail: `mount failed: ${describeMountError(err)}` }));
7853
7943
  results.push(
7854
7944
  await raceMountDeadline(work, deadlineMs, () => ({ id: `composition:${exp.pairKey}:renders`, pass: false, detail: `mount deadline exceeded (${deadlineMs}ms)` }))
7855
7945
  );
@@ -7871,6 +7961,7 @@ var init_composition = __esm({
7871
7961
  init_candidate_css();
7872
7962
  init_font_faces();
7873
7963
  init_image_diff();
7964
+ init_mount_error();
7874
7965
  init_mount_limits();
7875
7966
  init_paths();
7876
7967
  REAL_JSX_SPEC = "__tendril_real_jsx__";
@@ -7890,17 +7981,17 @@ export const jsxs = (t, p, k) => real.jsxs(t, inject(t, p), k);
7890
7981
  });
7891
7982
 
7892
7983
  // packages/verify/src/occlusion.ts
7893
- import { existsSync as existsSync17 } from "node:fs";
7894
- import path23 from "node:path";
7984
+ import { existsSync as existsSync18 } from "node:fs";
7985
+ import path24 from "node:path";
7895
7986
  import { build as build7 } from "esbuild";
7896
7987
  import { chromium as chromium8 } from "playwright-core";
7897
7988
  async function compileTwoUp(task, bundleDir) {
7898
- const entryTsx = path23.join(bundleDir, task.entry);
7899
- if (!existsSync17(entryTsx)) return { error: `${task.entry} missing` };
7989
+ const entryTsx = path24.join(bundleDir, task.entry);
7990
+ if (!existsSync18(entryTsx)) return { error: `${task.entry} missing` };
7900
7991
  const src = `
7901
7992
  import { createElement } from "react";
7902
7993
  import { createRoot } from "react-dom/client";
7903
- import * as B from ${JSON.stringify(path23.resolve(entryTsx))};
7994
+ import * as B from ${JSON.stringify(path24.resolve(entryTsx))};
7904
7995
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
7905
7996
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
7906
7997
  for (const id of ["first", "second"]) {
@@ -8137,6 +8228,7 @@ var init_src5 = __esm({
8137
8228
  init_tasks();
8138
8229
  init_prelude();
8139
8230
  init_mount_limits();
8231
+ init_mount_error();
8140
8232
  init_font_collection();
8141
8233
  init_font_discovery();
8142
8234
  init_font_faces();
@@ -8151,23 +8243,23 @@ var init_src5 = __esm({
8151
8243
  });
8152
8244
 
8153
8245
  // packages/cli/src/environment.ts
8154
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "node:fs";
8155
- import path24 from "node:path";
8246
+ import { existsSync as existsSync19, readFileSync as readFileSync17 } from "node:fs";
8247
+ import path25 from "node:path";
8156
8248
  import { createHash as createHash5 } from "node:crypto";
8157
8249
  import { fileURLToPath as fileURLToPath5 } from "node:url";
8158
8250
  function cliVersion() {
8159
8251
  try {
8160
- return JSON.parse(readFileSync16(path24.join(path24.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
8252
+ return JSON.parse(readFileSync17(path25.join(path25.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
8161
8253
  } catch {
8162
8254
  return "dev";
8163
8255
  }
8164
8256
  }
8165
8257
  function environmentStamp(taskFamilies) {
8166
- const manifestPath2 = path24.join(fontCacheDir(), "manifest.json");
8258
+ const manifestPath2 = path25.join(fontCacheDir(), "manifest.json");
8167
8259
  let fontsHash = null;
8168
- if (existsSync18(manifestPath2)) {
8260
+ if (existsSync19(manifestPath2)) {
8169
8261
  try {
8170
- const entries = JSON.parse(readFileSync16(manifestPath2, "utf8"));
8262
+ const entries = JSON.parse(readFileSync17(manifestPath2, "utf8"));
8171
8263
  const scoped = taskFamilies === void 0 || taskFamilies === null ? entries : entries.filter((f) => taskFamilies.some((fam) => fam.toLowerCase() === f.family.toLowerCase()));
8172
8264
  const faces = scoped.map((f) => `${f.family}:${f.weight}:${f.sha256}`).sort();
8173
8265
  fontsHash = faces.length === 0 ? null : createHash5("sha256").update(faces.join("\n")).digest("hex").slice(0, 16);
@@ -8211,8 +8303,8 @@ var init_describe = __esm({
8211
8303
  });
8212
8304
 
8213
8305
  // packages/cli/src/env.ts
8214
- import { existsSync as existsSync19, readFileSync as readFileSync17 } from "node:fs";
8215
- import path25 from "node:path";
8306
+ import { existsSync as existsSync20, readFileSync as readFileSync18 } from "node:fs";
8307
+ import path26 from "node:path";
8216
8308
  function parseEnv(content) {
8217
8309
  const entries = /* @__PURE__ */ new Map();
8218
8310
  for (const line of content.split("\n")) {
@@ -8224,9 +8316,9 @@ function parseEnv(content) {
8224
8316
  function resolveCredential(name) {
8225
8317
  const fromProcess = process.env[name];
8226
8318
  if (fromProcess) return fromProcess;
8227
- const envPath = path25.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
8228
- if (!existsSync19(envPath)) return void 0;
8229
- return parseEnv(readFileSync17(envPath, "utf8")).get(name);
8319
+ const envPath = path26.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
8320
+ if (!existsSync20(envPath)) return void 0;
8321
+ return parseEnv(readFileSync18(envPath, "utf8")).get(name);
8230
8322
  }
8231
8323
  var init_env = __esm({
8232
8324
  "packages/cli/src/env.ts"() {
@@ -8286,16 +8378,16 @@ var init_output = __esm({
8286
8378
  });
8287
8379
 
8288
8380
  // 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";
8381
+ import { chmodSync, existsSync as existsSync21, mkdirSync as mkdirSync4, readFileSync as readFileSync19, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
8290
8382
  import os4 from "node:os";
8291
- import path26 from "node:path";
8383
+ import path27 from "node:path";
8292
8384
  function sessionPath() {
8293
- return process.env["TENDRIL_SESSION_PATH"] ?? path26.join(os4.homedir(), ".tendril", "session.json");
8385
+ return process.env["TENDRIL_SESSION_PATH"] ?? path27.join(os4.homedir(), ".tendril", "session.json");
8294
8386
  }
8295
8387
  function readStoredSession(file = sessionPath()) {
8296
- if (!existsSync20(file)) return void 0;
8388
+ if (!existsSync21(file)) return void 0;
8297
8389
  try {
8298
- const parsed = JSON.parse(readFileSync18(file, "utf8"));
8390
+ const parsed = JSON.parse(readFileSync19(file, "utf8"));
8299
8391
  if (typeof parsed.origin !== "string" || typeof parsed.token !== "string") return void 0;
8300
8392
  return { origin: parsed.origin, token: parsed.token };
8301
8393
  } catch {
@@ -8303,13 +8395,13 @@ function readStoredSession(file = sessionPath()) {
8303
8395
  }
8304
8396
  }
8305
8397
  function writeStoredSession(session, file = sessionPath()) {
8306
- mkdirSync4(path26.dirname(file), { recursive: true });
8307
- writeFileSync7(file, `${JSON.stringify(session, null, 2)}
8398
+ mkdirSync4(path27.dirname(file), { recursive: true });
8399
+ writeFileSync8(file, `${JSON.stringify(session, null, 2)}
8308
8400
  `, { mode: 384 });
8309
8401
  chmodSync(file, 384);
8310
8402
  }
8311
8403
  function clearStoredSession(file = sessionPath()) {
8312
- if (existsSync20(file)) rmSync3(file);
8404
+ if (existsSync21(file)) rmSync3(file);
8313
8405
  }
8314
8406
  function tokenFor(origin, file = sessionPath()) {
8315
8407
  const fromEnv = process.env["TENDRIL_TOKEN"];
@@ -8468,17 +8560,17 @@ var init_publish_client = __esm({
8468
8560
  });
8469
8561
 
8470
8562
  // packages/cli/src/entitlement.ts
8471
- import { chmodSync as chmodSync2, existsSync as existsSync21, mkdirSync as mkdirSync5, readFileSync as readFileSync19, writeFileSync as writeFileSync8 } from "node:fs";
8563
+ import { chmodSync as chmodSync2, existsSync as existsSync22, mkdirSync as mkdirSync5, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "node:fs";
8472
8564
  import crypto from "node:crypto";
8473
8565
  import os5 from "node:os";
8474
- import path27 from "node:path";
8566
+ import path28 from "node:path";
8475
8567
  function entitlementPath() {
8476
- return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path27.join(os5.homedir(), ".tendril", "entitlement.json");
8568
+ return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path28.join(os5.homedir(), ".tendril", "entitlement.json");
8477
8569
  }
8478
8570
  function readStoredEntitlement(file = entitlementPath()) {
8479
- if (!existsSync21(file)) return void 0;
8571
+ if (!existsSync22(file)) return void 0;
8480
8572
  try {
8481
- const parsed = JSON.parse(readFileSync19(file, "utf8"));
8573
+ const parsed = JSON.parse(readFileSync20(file, "utf8"));
8482
8574
  if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
8483
8575
  return { token: parsed.token, lastRefreshAt: parsed.lastRefreshAt };
8484
8576
  } catch {
@@ -8486,8 +8578,8 @@ function readStoredEntitlement(file = entitlementPath()) {
8486
8578
  }
8487
8579
  }
8488
8580
  function writeStoredEntitlement(stored, file = entitlementPath()) {
8489
- mkdirSync5(path27.dirname(file), { recursive: true });
8490
- writeFileSync8(file, `${JSON.stringify(stored, null, 2)}
8581
+ mkdirSync5(path28.dirname(file), { recursive: true });
8582
+ writeFileSync9(file, `${JSON.stringify(stored, null, 2)}
8491
8583
  `);
8492
8584
  chmodSync2(file, 384);
8493
8585
  }
@@ -8571,9 +8663,9 @@ var init_entitlement = __esm({
8571
8663
 
8572
8664
  // packages/cli/src/commands/doctor.ts
8573
8665
  import { spawnSync } from "node:child_process";
8574
- import { existsSync as existsSync22, readFileSync as readFileSync20, readdirSync as readdirSync8 } from "node:fs";
8666
+ import { existsSync as existsSync23, readFileSync as readFileSync21, readdirSync as readdirSync8 } from "node:fs";
8575
8667
  import os6 from "node:os";
8576
- import path28 from "node:path";
8668
+ import path29 from "node:path";
8577
8669
  function withDeadline(work, ms) {
8578
8670
  return Promise.race([
8579
8671
  work,
@@ -8633,17 +8725,17 @@ async function runDoctorChecks(options) {
8633
8725
  remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
8634
8726
  });
8635
8727
  }
8636
- const fontManifest = path28.join(fontCacheDir(), "manifest.json");
8728
+ const fontManifest = path29.join(fontCacheDir(), "manifest.json");
8637
8729
  checks.push(
8638
- existsSync22(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync20(fontManifest, "utf8")).length} faces)` } : {
8730
+ existsSync23(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync21(fontManifest, "utf8")).length} faces)` } : {
8639
8731
  name: "font-cache",
8640
8732
  ok: true,
8641
8733
  detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
8642
8734
  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.`
8643
8735
  }
8644
8736
  );
8645
- const pluginRoot = path28.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
8646
- if (existsSync22(pluginRoot)) {
8737
+ const pluginRoot = path29.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
8738
+ if (existsSync23(pluginRoot)) {
8647
8739
  try {
8648
8740
  const versions = readdirSync8(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
8649
8741
  const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
@@ -9915,8 +10007,8 @@ var init_engine_curated = __esm({
9915
10007
  });
9916
10008
 
9917
10009
  // packages/generate/src/loop.ts
9918
- import { existsSync as existsSync24, mkdirSync as mkdirSync7, readFileSync as readFileSync22, renameSync, writeFileSync as writeFileSync10 } from "node:fs";
9919
- import path31 from "node:path";
10010
+ import { existsSync as existsSync25, mkdirSync as mkdirSync7, readFileSync as readFileSync23, renameSync, writeFileSync as writeFileSync11 } from "node:fs";
10011
+ import path32 from "node:path";
9920
10012
  import { z as z13 } from "zod";
9921
10013
  function objective(scores, behaviors) {
9922
10014
  const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
@@ -9958,9 +10050,9 @@ ${absentLines.join("\n")}` : ""}
9958
10050
  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."}`;
9959
10051
  }
9960
10052
  function archivePriorRun(outDir) {
9961
- if (!existsSync24(path31.join(outDir, "run-log.json")) && !existsSync24(path31.join(outDir, "loop-state.json"))) return void 0;
10053
+ if (!existsSync25(path32.join(outDir, "run-log.json")) && !existsSync25(path32.join(outDir, "loop-state.json"))) return void 0;
9962
10054
  let n = 1;
9963
- while (existsSync24(`${outDir}-prev-${n}`)) n += 1;
10055
+ while (existsSync25(`${outDir}-prev-${n}`)) n += 1;
9964
10056
  renameSync(outDir, `${outDir}-prev-${n}`);
9965
10057
  return `${outDir}-prev-${n}`;
9966
10058
  }
@@ -9969,14 +10061,14 @@ async function runEngineLoop(opts) {
9969
10061
  const plateau = opts.plateau ?? 2;
9970
10062
  const progress = opts.onProgress ?? (() => {
9971
10063
  });
9972
- const statePath = path31.join(opts.outDir, "loop-state.json");
9973
- const resuming = opts.resume === true && existsSync24(statePath);
10064
+ const statePath = path32.join(opts.outDir, "loop-state.json");
10065
+ const resuming = opts.resume === true && existsSync25(statePath);
9974
10066
  if (!resuming) {
9975
10067
  const archived = archivePriorRun(opts.outDir);
9976
10068
  if (archived !== void 0) progress(`previous run archived to ${archived}`);
9977
10069
  }
9978
10070
  mkdirSync7(opts.outDir, { recursive: true });
9979
- const scratch = path31.join(opts.outDir, ".candidate");
10071
+ const scratch = path32.join(opts.outDir, ".candidate");
9980
10072
  let attempts = [];
9981
10073
  let log = [];
9982
10074
  let best;
@@ -9984,7 +10076,7 @@ async function runEngineLoop(opts) {
9984
10076
  let nonAccepted = 0;
9985
10077
  let stopReason = "max-iterations";
9986
10078
  if (resuming) {
9987
- const restored = LoopStateSchema.parse(JSON.parse(readFileSync22(statePath, "utf8")));
10079
+ const restored = LoopStateSchema.parse(JSON.parse(readFileSync23(statePath, "utf8")));
9988
10080
  attempts = restored.attempts;
9989
10081
  log = restored.iterations;
9990
10082
  spentUsd = restored.spentUsd;
@@ -9999,12 +10091,12 @@ async function runEngineLoop(opts) {
9999
10091
  progress(`resumed: ${log.length} iteration(s), $${spentUsd.toFixed(3)} spent, best pass=${best?.objective[0] ?? 0}`);
10000
10092
  }
10001
10093
  const persist = () => {
10002
- writeFileSync10(statePath, `${JSON.stringify({ version: 1, spentUsd, attempts, iterations: log }, null, 1)}
10094
+ writeFileSync11(statePath, `${JSON.stringify({ version: 1, spentUsd, attempts, iterations: log }, null, 1)}
10003
10095
  `);
10004
10096
  };
10005
10097
  const writeCandidate = (files) => {
10006
10098
  mkdirSync7(scratch, { recursive: true });
10007
- for (const [name, content] of Object.entries(files)) writeFileSync10(path31.join(scratch, name), content);
10099
+ for (const [name, content] of Object.entries(files)) writeFileSync11(path32.join(scratch, name), content);
10008
10100
  };
10009
10101
  const scoreCandidate = async (candidate, iter, usd, modelMs) => {
10010
10102
  writeCandidate(candidate.files);
@@ -10062,8 +10154,8 @@ async function runEngineLoop(opts) {
10062
10154
  const usd = candidate.usage?.usd ?? 0;
10063
10155
  spentUsd += usd;
10064
10156
  if (candidate.raw !== void 0) {
10065
- mkdirSync7(path31.join(opts.outDir, "responses"), { recursive: true });
10066
- writeFileSync10(path31.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
10157
+ mkdirSync7(path32.join(opts.outDir, "responses"), { recursive: true });
10158
+ writeFileSync11(path32.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
10067
10159
  }
10068
10160
  if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
10069
10161
  const finish = candidate.usage?.finishReason ?? "?";
@@ -10089,10 +10181,10 @@ async function runEngineLoop(opts) {
10089
10181
  }
10090
10182
  }
10091
10183
  }
10092
- if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(path31.join(opts.outDir, name), content);
10184
+ if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync11(path32.join(opts.outDir, name), content);
10093
10185
  const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
10094
- writeFileSync10(
10095
- path31.join(opts.outDir, "run-log.json"),
10186
+ writeFileSync11(
10187
+ path32.join(opts.outDir, "run-log.json"),
10096
10188
  `${JSON.stringify(
10097
10189
  {
10098
10190
  ...opts.meta,
@@ -10159,8 +10251,8 @@ var init_loop2 = __esm({
10159
10251
  });
10160
10252
 
10161
10253
  // packages/generate/src/brief.ts
10162
- import { existsSync as existsSync25, readFileSync as readFileSync23 } from "node:fs";
10163
- import path32 from "node:path";
10254
+ import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
10255
+ import path33 from "node:path";
10164
10256
  import { PNG as PNG3 } from "pngjs";
10165
10257
  function singleAxes2(name) {
10166
10258
  const parsed = parseVariantAxes(name);
@@ -10600,15 +10692,15 @@ function authorBehaviors(api, extras = {}) {
10600
10692
  };
10601
10693
  }
10602
10694
  function envelopeText(file) {
10603
- return envelopeFirstTextPart(JSON.parse(readFileSync23(file, "utf8")));
10695
+ return envelopeFirstTextPart(JSON.parse(readFileSync24(file, "utf8")));
10604
10696
  }
10605
10697
  function metadataText(file) {
10606
- return envelopeTextContent(JSON.parse(readFileSync23(file, "utf8")));
10698
+ return envelopeTextContent(JSON.parse(readFileSync24(file, "utf8")));
10607
10699
  }
10608
10700
  function dismissEvidence(setDir, repSlugs) {
10609
10701
  for (const slug of repSlugs) {
10610
- const f = path32.join(setDir, slug, "get_design_context.json");
10611
- if (!existsSync25(f)) continue;
10702
+ const f = path33.join(setDir, slug, "get_design_context.json");
10703
+ if (!existsSync26(f)) continue;
10612
10704
  const text = envelopeText(f);
10613
10705
  const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*true\b/i.exec(text);
10614
10706
  if (propHit !== null) return { evidence: `emission prop "${propHit[1]}"`, visibleReps: [] };
@@ -10635,9 +10727,9 @@ function dismissEvidence(setDir, repSlugs) {
10635
10727
  const glyphIsTheComponent = (() => {
10636
10728
  const slugToCheck = vis.visibleIn[0];
10637
10729
  if (slugToCheck === void 0) return false;
10638
- const metaFile = path32.join(setDir, slugToCheck, "get_metadata.json");
10730
+ const metaFile = path33.join(setDir, slugToCheck, "get_metadata.json");
10639
10731
  try {
10640
- const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync23(metaFile, "utf8"))));
10732
+ const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync24(metaFile, "utf8"))));
10641
10733
  if (root.children.length !== 1) return false;
10642
10734
  const contains = (n) => normalizedLayerName(n.name) === norm2 || n.children.some(contains);
10643
10735
  return contains(root.children[0]);
@@ -10657,10 +10749,10 @@ function dismissEvidence(setDir, repSlugs) {
10657
10749
  return void 0;
10658
10750
  }
10659
10751
  function recordedReferencePng(setDir, slug) {
10660
- const f = path32.join(setDir, slug, "get_screenshot.json");
10661
- if (!existsSync25(f)) return void 0;
10752
+ const f = path33.join(setDir, slug, "get_screenshot.json");
10753
+ if (!existsSync26(f)) return void 0;
10662
10754
  try {
10663
- const env = JSON.parse(readFileSync23(f, "utf8")).content.find((c) => c.type === "image");
10755
+ const env = JSON.parse(readFileSync24(f, "utf8")).content.find((c) => c.type === "image");
10664
10756
  return env?.data === void 0 ? void 0 : Uint8Array.from(Buffer.from(env.data, "base64"));
10665
10757
  } catch {
10666
10758
  return void 0;
@@ -10740,13 +10832,13 @@ function recordedFontNeeds(setDir, opts = {}) {
10740
10832
  }
10741
10833
  }
10742
10834
  const manifest = loadManifest(setDir);
10743
- const setDefs = path32.join(setDir, "get_variable_defs.json");
10744
- if (existsSync25(setDefs)) fromDefs(envelopeText(setDefs));
10835
+ const setDefs = path33.join(setDir, "get_variable_defs.json");
10836
+ if (existsSync26(setDefs)) fromDefs(envelopeText(setDefs));
10745
10837
  for (const rep of manifest.reps) {
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));
10838
+ const ctx = path33.join(setDir, rep.slug, "get_design_context.json");
10839
+ if (existsSync26(ctx)) fromEmission(envelopeText(ctx));
10840
+ const defs = path33.join(setDir, rep.slug, "get_variable_defs.json");
10841
+ if (existsSync26(defs)) fromDefs(envelopeText(defs));
10750
10842
  }
10751
10843
  return [...byFamily.entries()].map(([family, paired]) => {
10752
10844
  const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
@@ -10757,10 +10849,10 @@ function symbolFontGlyphCount(setDir, reps) {
10757
10849
  const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
10758
10850
  const glyphs = /* @__PURE__ */ new Set();
10759
10851
  for (const rep of reps) {
10760
- const file = path32.join(setDir, rep, "get_metadata.json");
10761
- if (!existsSync25(file)) continue;
10852
+ const file = path33.join(setDir, rep, "get_metadata.json");
10853
+ if (!existsSync26(file)) continue;
10762
10854
  try {
10763
- const text = JSON.parse(readFileSync23(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
10855
+ const text = JSON.parse(readFileSync24(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
10764
10856
  for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
10765
10857
  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)));
10766
10858
  if (PUA.test(name)) glyphs.add(name);
@@ -10786,8 +10878,8 @@ function recordedTextSlots(setDir, repSlugs) {
10786
10878
  const propRep = [];
10787
10879
  const perRep = [];
10788
10880
  for (const slug of repSlugs) {
10789
- const f = path32.join(setDir, slug, "get_design_context.json");
10790
- if (!existsSync25(f)) continue;
10881
+ const f = path33.join(setDir, slug, "get_design_context.json");
10882
+ if (!existsSync26(f)) continue;
10791
10883
  const code = envelopeText(f);
10792
10884
  const props = /* @__PURE__ */ new Map();
10793
10885
  for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
@@ -10813,8 +10905,8 @@ function recordedTextSlots(setDir, repSlugs) {
10813
10905
  const axisValuesBySlug = /* @__PURE__ */ new Map();
10814
10906
  const valuesByAxis = /* @__PURE__ */ new Map();
10815
10907
  for (const slug of repSlugs) {
10816
- const metaFile = path32.join(setDir, slug, "get_metadata.json");
10817
- if (!existsSync25(metaFile)) continue;
10908
+ const metaFile = path33.join(setDir, slug, "get_metadata.json");
10909
+ if (!existsSync26(metaFile)) continue;
10818
10910
  const name = symbolName(metadataText(metaFile));
10819
10911
  if (name === void 0) continue;
10820
10912
  const values = /* @__PURE__ */ new Set();
@@ -10929,8 +11021,8 @@ function authorTaskFromSet(setDir, opts = {}) {
10929
11021
  const poses = [];
10930
11022
  const missing = [];
10931
11023
  for (const rep of manifest.reps) {
10932
- const metaFile = path32.join(setDir, rep.slug, "get_metadata.json");
10933
- if (!existsSync25(metaFile)) {
11024
+ const metaFile = path33.join(setDir, rep.slug, "get_metadata.json");
11025
+ if (!existsSync26(metaFile)) {
10934
11026
  missing.push(rep.slug);
10935
11027
  continue;
10936
11028
  }
@@ -10944,8 +11036,8 @@ function authorTaskFromSet(setDir, opts = {}) {
10944
11036
  if (missing.length > 0) {
10945
11037
  throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
10946
11038
  }
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);
11039
+ const setMeta = path33.join(setDir, "get_metadata.json");
11040
+ const latticeNames = manifest.latticeNames ?? (existsSync26(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
10949
11041
  const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
10950
11042
  const recordedFonts = recordedFontFamilies(setDir);
10951
11043
  const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
@@ -11146,17 +11238,17 @@ var init_brief = __esm({
11146
11238
  });
11147
11239
 
11148
11240
  // packages/generate/src/segments.ts
11149
- import { existsSync as existsSync26, readFileSync as readFileSync24, readdirSync as readdirSync10 } from "node:fs";
11150
- import path33 from "node:path";
11241
+ import { existsSync as existsSync27, readFileSync as readFileSync25, readdirSync as readdirSync10 } from "node:fs";
11242
+ import path34 from "node:path";
11151
11243
  function repText(set, rep, tool) {
11152
- const env = JSON.parse(readFileSync24(path33.join(set, rep, `${tool}.json`), "utf8"));
11244
+ const env = JSON.parse(readFileSync25(path34.join(set, rep, `${tool}.json`), "utf8"));
11153
11245
  return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
11154
11246
  }
11155
11247
  function refPngDims(set, rep) {
11156
- const f = path33.join(set, rep, "get_screenshot.json");
11157
- if (!existsSync26(f)) return void 0;
11248
+ const f = path34.join(set, rep, "get_screenshot.json");
11249
+ if (!existsSync27(f)) return void 0;
11158
11250
  try {
11159
- const env = JSON.parse(readFileSync24(f, "utf8")).content.find((c) => c.type === "image");
11251
+ const env = JSON.parse(readFileSync25(f, "utf8")).content.find((c) => c.type === "image");
11160
11252
  if (env?.data === void 0) return void 0;
11161
11253
  const buf = Buffer.from(env.data, "base64");
11162
11254
  if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return void 0;
@@ -11222,20 +11314,20 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
11222
11314
  }
11223
11315
  function buildSegments(task, mode = "fenced") {
11224
11316
  const SET = task.set;
11225
- let defsRecorded = existsSync26(path33.join(SET, "get_variable_defs.json"));
11317
+ let defsRecorded = existsSync27(path34.join(SET, "get_variable_defs.json"));
11226
11318
  let rawDefs = {};
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"))) || "{}";
11319
+ if (existsSync27(path34.join(SET, "get_variable_defs.json"))) {
11320
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync25(path34.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
11229
11321
  try {
11230
11322
  rawDefs = JSON.parse(text);
11231
11323
  } catch {
11232
11324
  }
11233
11325
  } else {
11234
11326
  for (const cfg of task.configs) {
11235
- const f = path33.join(SET, cfg.rep, "get_variable_defs.json");
11236
- if (!existsSync26(f)) continue;
11327
+ const f = path34.join(SET, cfg.rep, "get_variable_defs.json");
11328
+ if (!existsSync27(f)) continue;
11237
11329
  defsRecorded = true;
11238
- const text = envelopeFirstTextPart(JSON.parse(readFileSync24(f, "utf8"))) || "{}";
11330
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync25(f, "utf8"))) || "{}";
11239
11331
  try {
11240
11332
  for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
11241
11333
  } catch {
@@ -11243,8 +11335,8 @@ function buildSegments(task, mode = "fenced") {
11243
11335
  }
11244
11336
  }
11245
11337
  const emissionTexts = task.configs.map((cfg) => {
11246
- const f = path33.join(SET, cfg.rep, "get_design_context.json");
11247
- return existsSync26(f) ? envelopeFirstTextPart(JSON.parse(readFileSync24(f, "utf8"))) : "";
11338
+ const f = path34.join(SET, cfg.rep, "get_design_context.json");
11339
+ return existsSync27(f) ? envelopeFirstTextPart(JSON.parse(readFileSync25(f, "utf8"))) : "";
11248
11340
  });
11249
11341
  const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
11250
11342
  const defs = JSON.stringify(map, null, 1);
@@ -11262,9 +11354,9 @@ TOKEN MAP NEVER RECORDED: this set holds no get_variable_defs envelope, so wheth
11262
11354
  for (const cfg of task.configs) {
11263
11355
  const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
11264
11356
  const emission = stripFigmaInstructions(repText(SET, cfg.rep, "get_design_context"));
11265
- const assets = readdirSync10(path33.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
11357
+ const assets = readdirSync10(path34.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
11266
11358
  \`\`\`svg
11267
- ${readFileSync24(path33.join(SET, cfg.rep, f), "utf8")}
11359
+ ${readFileSync25(path34.join(SET, cfg.rep, f), "utf8")}
11268
11360
  \`\`\``).join("\n");
11269
11361
  const refNote = (() => {
11270
11362
  const dims = refPngDims(SET, cfg.rep);
@@ -11300,7 +11392,7 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
11300
11392
  } else {
11301
11393
  parts.push(`
11302
11394
  ## Output format
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.`);
11395
+ Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path34.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.`);
11304
11396
  }
11305
11397
  return parts.join("\n");
11306
11398
  }
@@ -11368,8 +11460,8 @@ var init_adapter = __esm({
11368
11460
 
11369
11461
  // packages/generate/src/bundle-emit.ts
11370
11462
  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";
11463
+ import { copyFileSync, existsSync as existsSync28, mkdirSync as mkdirSync8, readFileSync as readFileSync26, readdirSync as readdirSync11, rmSync as rmSync4, writeFileSync as writeFileSync12 } from "node:fs";
11464
+ import path35 from "node:path";
11373
11465
  function pinFromConfigs(configs) {
11374
11466
  const domains = /* @__PURE__ */ new Map();
11375
11467
  const kinds = /* @__PURE__ */ new Map();
@@ -11438,9 +11530,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11438
11530
  const notices = [];
11439
11531
  const licenseTexts = /* @__PURE__ */ new Map();
11440
11532
  for (const face of faces) {
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";
11533
+ const src = path35.join(cacheDir, path35.basename(face.file));
11534
+ const target = `./fonts/${path35.basename(face.file)}`;
11535
+ const format = FONT_FORMATS[path35.extname(face.file).toLowerCase()] ?? "truetype";
11444
11536
  const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
11445
11537
  const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
11446
11538
  const license = normalizeFontLicense(face.license);
@@ -11478,14 +11570,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11478
11570
  `/* ${decl} */`
11479
11571
  );
11480
11572
  }
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)));
11573
+ } else if (existsSync28(src) && createHash6("sha256").update(readFileSync26(src)).digest("hex") === face.sha256) {
11574
+ mkdirSync8(path35.join(bundleDir, "fonts"), { recursive: true });
11575
+ copyFileSync(src, path35.join(bundleDir, "fonts", path35.basename(face.file)));
11484
11576
  licenseTexts.set(terms.file, terms.text);
11485
11577
  const upstream = upstreamAttribution(face);
11486
11578
  notices.push(
11487
11579
  "",
11488
- `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path34.basename(face.file)}`,
11580
+ `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path35.basename(face.file)}`,
11489
11581
  ` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
11490
11582
  ` source: ${face.source}`,
11491
11583
  ` sha256: ${face.sha256}`,
@@ -11499,9 +11591,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11499
11591
  }
11500
11592
  if (lines.length === 0) return null;
11501
11593
  if (notices.length > 0) {
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")}
11594
+ const fontsDir = path35.join(bundleDir, "fonts");
11595
+ for (const [file, text] of licenseTexts) writeFileSync12(path35.join(fontsDir, file), text);
11596
+ writeFileSync12(path35.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
11505
11597
  `);
11506
11598
  header.push(
11507
11599
  "/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
@@ -11513,10 +11605,10 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11513
11605
  `;
11514
11606
  }
11515
11607
  function countLatticeSymbols(setDir) {
11516
- const manifestFile = path34.join(setDir, "recording-set.json");
11517
- if (existsSync27(manifestFile)) {
11608
+ const manifestFile = path35.join(setDir, "recording-set.json");
11609
+ if (existsSync28(manifestFile)) {
11518
11610
  try {
11519
- const stored = JSON.parse(readFileSync25(manifestFile, "utf8"));
11611
+ const stored = JSON.parse(readFileSync26(manifestFile, "utf8"));
11520
11612
  if (stored.variantScope !== "component-set") return null;
11521
11613
  const lattice = stored.latticeNames;
11522
11614
  if (lattice !== void 0 && lattice.length > 0) return lattice.length;
@@ -11524,13 +11616,13 @@ function countLatticeSymbols(setDir) {
11524
11616
  }
11525
11617
  }
11526
11618
  const files = [
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));
11619
+ path35.join(setDir, "get_metadata.json"),
11620
+ ...existsSync28(setDir) ? readdirSync11(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path35.join(setDir, f)) : []
11621
+ ].filter((f) => existsSync28(f));
11530
11622
  if (files.length === 0) return null;
11531
11623
  let count = 0;
11532
11624
  for (const f of files) {
11533
- const text = envelopeTextContent(JSON.parse(readFileSync25(f, "utf8")));
11625
+ const text = envelopeTextContent(JSON.parse(readFileSync26(f, "utf8")));
11534
11626
  count += [...text.matchAll(/name="([^"]*)"/g)].filter((m) => m[1].includes("=")).length;
11535
11627
  }
11536
11628
  return count > 0 ? count : null;
@@ -11538,21 +11630,21 @@ function countLatticeSymbols(setDir) {
11538
11630
  function recordingSetHash(setDir, configs) {
11539
11631
  const relPaths = [];
11540
11632
  for (const name of ["recording-set.json", "completeness-manifest.json", "get_variable_defs.json", "get_metadata.json"]) {
11541
- if (existsSync27(path34.join(setDir, name))) relPaths.push(name);
11633
+ if (existsSync28(path35.join(setDir, name))) relPaths.push(name);
11542
11634
  }
11543
11635
  for (const cfg of configs) {
11544
11636
  for (const f of ["get_design_context.json", "get_metadata.json", "get_screenshot.json", "get_variable_defs.json"]) {
11545
- if (existsSync27(path34.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
11637
+ if (existsSync28(path35.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
11546
11638
  }
11547
- if (existsSync27(path34.join(setDir, cfg.rep))) {
11548
- for (const asset of readdirSync11(path34.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
11639
+ if (existsSync28(path35.join(setDir, cfg.rep))) {
11640
+ for (const asset of readdirSync11(path35.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
11549
11641
  relPaths.push(`${cfg.rep}/${asset}`);
11550
11642
  }
11551
11643
  }
11552
11644
  }
11553
11645
  return hashRecordingSet(
11554
11646
  relPaths,
11555
- (p) => new Uint8Array(readFileSync25(path34.join(setDir, p))),
11647
+ (p) => new Uint8Array(readFileSync26(path35.join(setDir, p))),
11556
11648
  (chunks) => {
11557
11649
  const h = createHash6("sha256");
11558
11650
  for (const c of chunks) h.update(c);
@@ -11593,8 +11685,8 @@ function emitBundleV1(opts) {
11593
11685
  const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
11594
11686
  const parity = opts.behaviors.filter((b) => b.id.startsWith("parity:"));
11595
11687
  const contract = opts.behaviors.filter((b) => CONTRACT_CHECK_IDS.has(b.id));
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"));
11688
+ const cssFiles = ["styles.css", "tokens.css"].map((f) => path35.join(opts.bundleDir, f)).filter((f) => existsSync28(f));
11689
+ const families = cssFontFamilies(cssFiles.map((f) => readFileSync26(f, "utf8")).join("\n"));
11598
11690
  const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
11599
11691
  family: f.family,
11600
11692
  weight: f.weight,
@@ -11623,7 +11715,7 @@ function emitBundleV1(opts) {
11623
11715
  // resolvable via verify's --set override).
11624
11716
  path: (() => {
11625
11717
  const base = process.env["INIT_CWD"] ?? process.cwd();
11626
- const rel = path34.relative(base, opts.task.set);
11718
+ const rel = path35.relative(base, opts.task.set);
11627
11719
  return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
11628
11720
  })(),
11629
11721
  component: opts.componentName,
@@ -11660,25 +11752,25 @@ function emitBundleV1(opts) {
11660
11752
  })
11661
11753
  };
11662
11754
  const written = [];
11663
- const manifestPath2 = path34.join(opts.bundleDir, "component.json");
11664
- writeFileSync11(manifestPath2, `${JSON.stringify(manifest, null, 2)}
11755
+ const manifestPath2 = path35.join(opts.bundleDir, "component.json");
11756
+ writeFileSync12(manifestPath2, `${JSON.stringify(manifest, null, 2)}
11665
11757
  `);
11666
11758
  written.push(manifestPath2);
11667
- const stylesPath = path34.join(opts.bundleDir, "styles.css");
11668
- if (existsSync27(stylesPath)) {
11759
+ const stylesPath = path35.join(opts.bundleDir, "styles.css");
11760
+ if (existsSync28(stylesPath)) {
11669
11761
  const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
11670
- const current = readFileSync25(stylesPath, "utf8");
11762
+ const current = readFileSync26(stylesPath, "utf8");
11671
11763
  const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
11672
- writeFileSync11(stylesPath, `${comment}
11764
+ writeFileSync12(stylesPath, `${comment}
11673
11765
  ${stripped}`);
11674
11766
  written.push(stylesPath);
11675
11767
  }
11676
- const fontsCssPath = path34.join(opts.bundleDir, "fonts.css");
11677
- rmSync4(path34.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
11768
+ const fontsCssPath = path35.join(opts.bundleDir, "fonts.css");
11769
+ rmSync4(path35.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
11678
11770
  rmSync4(fontsCssPath, { force: true });
11679
11771
  const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
11680
11772
  if (fontsCss !== null) {
11681
- writeFileSync11(fontsCssPath, fontsCss);
11773
+ writeFileSync12(fontsCssPath, fontsCss);
11682
11774
  written.push(fontsCssPath);
11683
11775
  }
11684
11776
  const unrecorded = lattice === null ? null : Math.max(0, lattice - statuses.length);
@@ -12107,8 +12199,8 @@ DEALINGS IN THE FONT SOFTWARE.
12107
12199
 
12108
12200
  // packages/generate/src/compose-pins.ts
12109
12201
  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";
12202
+ import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync12, realpathSync as realpathSync3, statSync as statSync4 } from "node:fs";
12203
+ import path36 from "node:path";
12112
12204
  function bundleDirs(roots, depth = 4) {
12113
12205
  const found = [];
12114
12206
  const seen = /* @__PURE__ */ new Set();
@@ -12117,11 +12209,11 @@ function bundleDirs(roots, depth = 4) {
12117
12209
  try {
12118
12210
  key = realpathSync3(dir);
12119
12211
  } catch {
12120
- key = path35.resolve(dir);
12212
+ key = path36.resolve(dir);
12121
12213
  }
12122
12214
  if (seen.has(key)) return;
12123
12215
  seen.add(key);
12124
- if (existsSync28(path35.join(dir, "component.json"))) {
12216
+ if (existsSync29(path36.join(dir, "component.json"))) {
12125
12217
  found.push(key);
12126
12218
  return;
12127
12219
  }
@@ -12134,14 +12226,14 @@ function bundleDirs(roots, depth = 4) {
12134
12226
  }
12135
12227
  for (const e of entries) {
12136
12228
  if (e === "node_modules" || e.startsWith(".")) continue;
12137
- const full = path35.join(dir, e);
12229
+ const full = path36.join(dir, e);
12138
12230
  try {
12139
12231
  if (statSync4(full).isDirectory()) walk2(full, remaining - 1);
12140
12232
  } catch {
12141
12233
  }
12142
12234
  }
12143
12235
  };
12144
- for (const r of roots) walk2(path35.resolve(r), depth);
12236
+ for (const r of roots) walk2(path36.resolve(r), depth);
12145
12237
  return found;
12146
12238
  }
12147
12239
  function composedPins(hostSet, libraryRoots) {
@@ -12160,7 +12252,7 @@ function composedPins(hostSet, libraryRoots) {
12160
12252
  let pinned = false;
12161
12253
  const failures = [];
12162
12254
  for (const rel of partnerRels) {
12163
- const partnerSet = path35.resolve(hostSet, rel);
12255
+ const partnerSet = path36.resolve(hostSet, rel);
12164
12256
  let partnerTask;
12165
12257
  let partnerManifest;
12166
12258
  try {
@@ -12189,7 +12281,7 @@ function composedPins(hostSet, libraryRoots) {
12189
12281
  const wantHash = recordingSetHash(partnerSet, partnerTask.configs);
12190
12282
  const matches = candidates.filter((dir) => {
12191
12283
  try {
12192
- const parsed = readBundleManifest(readFileSync26(path35.join(dir, "component.json"), "utf8"));
12284
+ const parsed = readBundleManifest(readFileSync27(path36.join(dir, "component.json"), "utf8"));
12193
12285
  return parsed.manifest?.provenance.recordingSet.hash === wantHash;
12194
12286
  } catch {
12195
12287
  return false;
@@ -12202,13 +12294,13 @@ function composedPins(hostSet, libraryRoots) {
12202
12294
  continue;
12203
12295
  }
12204
12296
  if (matches.length > 1) {
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`);
12297
+ failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) => path36.basename(m)).join(", ")}) \u2014 ambiguous; remove or point --library away from the duplicates`);
12206
12298
  continue;
12207
12299
  }
12208
12300
  const bundleDir = matches[0];
12209
12301
  let manifest;
12210
12302
  try {
12211
- manifest = readBundleManifest(readFileSync26(path35.join(bundleDir, "component.json"), "utf8")).manifest;
12303
+ manifest = readBundleManifest(readFileSync27(path36.join(bundleDir, "component.json"), "utf8")).manifest;
12212
12304
  } catch {
12213
12305
  manifest = void 0;
12214
12306
  }
@@ -12225,8 +12317,8 @@ function composedPins(hostSet, libraryRoots) {
12225
12317
  const moduleFiles = [];
12226
12318
  let fileIssue;
12227
12319
  for (const name of [manifest.entry, "styles.css", "tokens.css", "fonts.css"]) {
12228
- const file = path35.join(bundleDir, name);
12229
- if (!existsSync28(file)) {
12320
+ const file = path36.join(bundleDir, name);
12321
+ if (!existsSync29(file)) {
12230
12322
  if (name === manifest.entry || name === "styles.css") {
12231
12323
  fileIssue = `${rel}: partner bundle is missing ${name}`;
12232
12324
  break;
@@ -12235,7 +12327,7 @@ function composedPins(hostSet, libraryRoots) {
12235
12327
  }
12236
12328
  let bytes;
12237
12329
  try {
12238
- bytes = readFileSync26(file);
12330
+ bytes = readFileSync27(file);
12239
12331
  } catch {
12240
12332
  fileIssue = `${rel}: partner file ${name} at ${bundleDir} is unreadable`;
12241
12333
  break;
@@ -12307,14 +12399,14 @@ function composedChecks(candidateDir, hostEntry, pins) {
12307
12399
  const checks = [];
12308
12400
  let entrySource = "";
12309
12401
  try {
12310
- entrySource = readFileSync26(path35.join(candidateDir, hostEntry), "utf8");
12402
+ entrySource = readFileSync27(path36.join(candidateDir, hostEntry), "utf8");
12311
12403
  } catch {
12312
12404
  }
12313
- const candidateRoot = path35.resolve(candidateDir);
12405
+ const candidateRoot = path36.resolve(candidateDir);
12314
12406
  for (const pin of pins) {
12315
12407
  const dir = composedModuleDir(pin.partnerName);
12316
- const resolvedDir = path35.resolve(candidateDir, dir);
12317
- if (!resolvedDir.startsWith(candidateRoot + path35.sep)) {
12408
+ const resolvedDir = path36.resolve(candidateDir, dir);
12409
+ if (!resolvedDir.startsWith(candidateRoot + path36.sep)) {
12318
12410
  checks.push({ id: `composition:${pin.pairKey}:module-verbatim`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
12319
12411
  checks.push({ id: `composition:${pin.pairKey}:imported`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
12320
12412
  continue;
@@ -12325,12 +12417,12 @@ function composedChecks(candidateDir, hostEntry, pins) {
12325
12417
  wrong.push(`${f.name} refused (not a plain path segment)`);
12326
12418
  continue;
12327
12419
  }
12328
- const target = path35.join(candidateDir, dir, f.name);
12329
- if (!existsSync28(target)) {
12420
+ const target = path36.join(candidateDir, dir, f.name);
12421
+ if (!existsSync29(target)) {
12330
12422
  wrong.push(`${f.name} missing`);
12331
12423
  continue;
12332
12424
  }
12333
- const sha = createHash7("sha256").update(readFileSync26(target)).digest("hex");
12425
+ const sha = createHash7("sha256").update(readFileSync27(target)).digest("hex");
12334
12426
  if (sha !== f.sha256) wrong.push(`${f.name} differs from the pinned partner bytes`);
12335
12427
  }
12336
12428
  checks.push({
@@ -12355,10 +12447,10 @@ function rootClassesFor(emission, nodeId) {
12355
12447
  }
12356
12448
  function regionOverrides(hostSet, partnerSet, instances) {
12357
12449
  const read = (setDir, rep) => {
12358
- const f = path35.join(setDir, rep, "get_design_context.json");
12359
- if (!existsSync28(f)) return void 0;
12450
+ const f = path36.join(setDir, rep, "get_design_context.json");
12451
+ if (!existsSync29(f)) return void 0;
12360
12452
  try {
12361
- return envelopeFirstTextPart(JSON.parse(readFileSync26(f, "utf8")));
12453
+ return envelopeFirstTextPart(JSON.parse(readFileSync27(f, "utf8")));
12362
12454
  } catch {
12363
12455
  return void 0;
12364
12456
  }
@@ -12388,7 +12480,7 @@ var init_compose_pins = __esm({
12388
12480
  init_src4();
12389
12481
  init_brief();
12390
12482
  init_bundle_emit();
12391
- composedModuleDir = (partnerName) => path35.posix.join("composed", partnerName);
12483
+ composedModuleDir = (partnerName) => path36.posix.join("composed", partnerName);
12392
12484
  safeSegment = (s) => /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(s) && !s.includes("..");
12393
12485
  MAX_PINNED_FILE_BYTES = 1024 * 1024;
12394
12486
  CONTEXT_LAYOUT_TOKENS = /* @__PURE__ */ new Set(["shrink-0", "grow", "flex-1", "basis-0", "self-stretch", "w-full", "h-full"]);
@@ -12396,8 +12488,8 @@ var init_compose_pins = __esm({
12396
12488
  });
12397
12489
 
12398
12490
  // packages/generate/src/motion.ts
12399
- import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync13, statSync as statSync5 } from "node:fs";
12400
- import path36 from "node:path";
12491
+ import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync13, statSync as statSync5 } from "node:fs";
12492
+ import path37 from "node:path";
12401
12493
  function springProgress(u, bounce) {
12402
12494
  const decay = Math.log(100);
12403
12495
  if (bounce <= 0) {
@@ -12468,10 +12560,10 @@ function reportsNoMotion(text) {
12468
12560
  });
12469
12561
  }
12470
12562
  function motionTruthFor(setDir) {
12471
- const file = path36.join(setDir, "get_motion_context.json");
12472
- if (existsSync29(file) && usableEnvelope(file, "get_motion_context").ok) {
12563
+ const file = path37.join(setDir, "get_motion_context.json");
12564
+ if (existsSync30(file) && usableEnvelope(file, "get_motion_context").ok) {
12473
12565
  try {
12474
- const text = envelopeTextContent(JSON.parse(readFileSync27(file, "utf8")));
12566
+ const text = envelopeTextContent(JSON.parse(readFileSync28(file, "utf8")));
12475
12567
  if (text.trim() === "") return { state: "recorded-empty" };
12476
12568
  return reportsNoMotion(text) ? { state: "recorded-no-motion", text } : { state: "recorded", text };
12477
12569
  } catch {
@@ -12484,21 +12576,21 @@ function motionTruthFor(setDir) {
12484
12576
  }
12485
12577
  }
12486
12578
  function motionDisclosure(bundleDir, setDir) {
12487
- const sheets = ["styles.css", "tokens.css"].map((f) => path36.join(bundleDir, f));
12488
- const composedRoot = path36.join(bundleDir, "composed");
12579
+ const sheets = ["styles.css", "tokens.css"].map((f) => path37.join(bundleDir, f));
12580
+ const composedRoot = path37.join(bundleDir, "composed");
12489
12581
  try {
12490
12582
  for (const entry of readdirSync13(composedRoot).sort()) {
12491
- const dir = path36.join(composedRoot, entry);
12583
+ const dir = path37.join(composedRoot, entry);
12492
12584
  try {
12493
12585
  if (!statSync5(dir).isDirectory()) continue;
12494
12586
  } catch {
12495
12587
  continue;
12496
12588
  }
12497
- sheets.push(path36.join(dir, "styles.css"), path36.join(dir, "tokens.css"));
12589
+ sheets.push(path37.join(dir, "styles.css"), path37.join(dir, "tokens.css"));
12498
12590
  }
12499
12591
  } catch {
12500
12592
  }
12501
- const css = sheets.filter((f) => existsSync29(f)).map((f) => readFileSync27(f, "utf8")).join("\n");
12593
+ const css = sheets.filter((f) => existsSync30(f)).map((f) => readFileSync28(f, "utf8")).join("\n");
12502
12594
  if (!MOTION_CSS_PATTERN.test(scannableCss(css))) return { present: false };
12503
12595
  return {
12504
12596
  present: true,
@@ -12865,12 +12957,12 @@ var init_components = __esm({
12865
12957
 
12866
12958
  // packages/generate/src/codebase/walk.ts
12867
12959
  import fs2 from "node:fs";
12868
- import path37 from "node:path";
12960
+ import path38 from "node:path";
12869
12961
  function resolvedPathIsExcluded(real, roots) {
12870
- if (isNeverRead(path37.basename(real))) return true;
12962
+ if (isNeverRead(path38.basename(real))) return true;
12871
12963
  for (const root of roots) {
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)) {
12964
+ if (real !== root && !real.startsWith(root + path38.sep)) continue;
12965
+ for (const segment of path38.relative(root, real).split(path38.sep).slice(0, -1)) {
12874
12966
  if (segment.startsWith(".") || EXCLUDED_DIRS.has(segment)) return true;
12875
12967
  }
12876
12968
  }
@@ -12884,7 +12976,7 @@ function containedRealpath(abs, roots) {
12884
12976
  return null;
12885
12977
  }
12886
12978
  for (const root of roots) {
12887
- if (real === root || real.startsWith(root + path37.sep)) return real;
12979
+ if (real === root || real.startsWith(root + path38.sep)) return real;
12888
12980
  }
12889
12981
  return null;
12890
12982
  }
@@ -12917,7 +13009,7 @@ function walkRepo(roots, limits, accept) {
12917
13009
  continue;
12918
13010
  }
12919
13011
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
12920
- const abs = path37.join(frame.dir, entry.name);
13012
+ const abs = path38.join(frame.dir, entry.name);
12921
13013
  if (EXCLUDED_DIRS.has(entry.name)) continue;
12922
13014
  if (isNeverRead(entry.name)) continue;
12923
13015
  const real = containedRealpath(abs, realRoots);
@@ -13005,18 +13097,18 @@ var init_walk = __esm({
13005
13097
  /^\.netrc$/i
13006
13098
  ];
13007
13099
  isNeverRead = (basename) => NEVER_READ.some((re) => re.test(basename));
13008
- toRel = (root, abs) => path37.relative(root, abs).split(path37.sep).join(path37.posix.sep);
13100
+ toRel = (root, abs) => path38.relative(root, abs).split(path38.sep).join(path38.posix.sep);
13009
13101
  }
13010
13102
  });
13011
13103
 
13012
13104
  // packages/generate/src/codebase/scan.ts
13013
13105
  import crypto2 from "node:crypto";
13014
13106
  import fs3 from "node:fs";
13015
- import path38 from "node:path";
13107
+ import path39 from "node:path";
13016
13108
  import postcss3 from "postcss";
13017
13109
  function scanCodebase(options) {
13018
13110
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
13019
- const roots = options.roots.map((r) => path38.resolve(r));
13111
+ const roots = options.roots.map((r) => path39.resolve(r));
13020
13112
  const walk2 = walkRepo(
13021
13113
  roots,
13022
13114
  {
@@ -13036,7 +13128,7 @@ function scanCodebase(options) {
13036
13128
  let bytesRead = 0;
13037
13129
  let filesRead = 0;
13038
13130
  for (const file of walk2.files) {
13039
- const base = path38.posix.basename(file.rel);
13131
+ const base = path39.posix.basename(file.rel);
13040
13132
  configFiles.add(file.rel);
13041
13133
  if (/^tailwind\.config\./.test(base) || file.rel === "babel.config.js") continue;
13042
13134
  const text = readTextFile(file.abs);
@@ -13052,7 +13144,7 @@ function scanCodebase(options) {
13052
13144
  }
13053
13145
  const css = extractCssCustomProperties(cssFiles.filter((f) => !f.rel.includes("..")));
13054
13146
  const components = scanComponents(componentFiles);
13055
- const packages = manifests.filter((m) => path38.posix.basename(m.rel) === "package.json");
13147
+ const packages = manifests.filter((m) => path39.posix.basename(m.rel) === "package.json");
13056
13148
  const styling = detectStyling(configFiles, cssFiles, componentFiles, manifests);
13057
13149
  const classNameStyle = representativeClassNames(cssFiles, css.unparsed.length);
13058
13150
  const disclosures = buildDisclosures(
@@ -13095,13 +13187,13 @@ function scanCodebase(options) {
13095
13187
  },
13096
13188
  components: {
13097
13189
  entries: components.entries.slice(0, PROFILE_LIMITS.maxComponents),
13098
- fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path38.posix.basename(f.rel)))),
13190
+ fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path39.posix.basename(f.rel)))),
13099
13191
  directoryLayout: buildHistogram(componentFiles.map((f) => classifyDirectoryLayout(f.rel))),
13100
13192
  exportStyle: buildHistogram(components.entries.map((e) => e.exportStyle)),
13101
13193
  classNameStyle,
13102
13194
  colocation: buildHistogram(collectColocation(componentFiles, cssFiles)),
13103
13195
  barrelFiles: componentFiles.filter(
13104
- (f) => /^index\.[tj]sx?$/.test(path38.posix.basename(f.rel)) && isReExportOnly(f.text)
13196
+ (f) => /^index\.[tj]sx?$/.test(path39.posix.basename(f.rel)) && isReExportOnly(f.text)
13105
13197
  ).length,
13106
13198
  refForwarding: {
13107
13199
  forwardRef: componentFiles.filter((f) => /\bforwardRef\s*[(<]/.test(f.text)).length,
@@ -13124,7 +13216,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
13124
13216
  };
13125
13217
  const deps = /* @__PURE__ */ new Map();
13126
13218
  for (const manifest of manifests) {
13127
- if (path38.posix.basename(manifest.rel) !== "package.json") continue;
13219
+ if (path39.posix.basename(manifest.rel) !== "package.json") continue;
13128
13220
  try {
13129
13221
  const parsed = JSON.parse(manifest.text);
13130
13222
  for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
@@ -13136,7 +13228,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
13136
13228
  }
13137
13229
  }
13138
13230
  for (const cfg of configFiles) {
13139
- const base = path38.posix.basename(cfg);
13231
+ const base = path39.posix.basename(cfg);
13140
13232
  if (/^tailwind\.config\./.test(base)) add("tailwind-v3", "file", cfg);
13141
13233
  if (base === "components.json") add("shadcn-style", "file", cfg);
13142
13234
  }
@@ -13194,8 +13286,8 @@ function collectClassNames(cssFiles) {
13194
13286
  return [...distinct].sort().map(classifyClassName);
13195
13287
  }
13196
13288
  function classifyDirectoryLayout(rel) {
13197
- const base = path38.posix.basename(rel).replace(/\.[^.]+$/, "");
13198
- const dir = path38.posix.basename(path38.posix.dirname(rel));
13289
+ const base = path39.posix.basename(rel).replace(/\.[^.]+$/, "");
13290
+ const dir = path39.posix.basename(path39.posix.dirname(rel));
13199
13291
  if (base === "index") return "component-dir";
13200
13292
  if (base === dir) return "component-dir";
13201
13293
  return "flat-file";
@@ -13219,7 +13311,7 @@ function detectFormatting(manifests, componentFiles) {
13219
13311
  (a, b) => a.rel.split("/").length - b.rel.split("/").length || a.rel.localeCompare(b.rel)
13220
13312
  );
13221
13313
  for (const manifest of byDepth) {
13222
- const base = path38.posix.basename(manifest.rel);
13314
+ const base = path39.posix.basename(manifest.rel);
13223
13315
  if (!/^\.prettierrc/.test(base) && base !== "package.json") continue;
13224
13316
  try {
13225
13317
  const parsed = JSON.parse(manifest.text);
@@ -13237,7 +13329,7 @@ function detectFormatting(manifests, componentFiles) {
13237
13329
  }
13238
13330
  }
13239
13331
  for (const manifest of byDepth) {
13240
- if (path38.posix.basename(manifest.rel) !== ".editorconfig") continue;
13332
+ if (path39.posix.basename(manifest.rel) !== ".editorconfig") continue;
13241
13333
  const style = /indent_style\s*=\s*(tab|space)/.exec(manifest.text)?.[1];
13242
13334
  const width = /indent_size\s*=\s*(\d+)/.exec(manifest.text)?.[1];
13243
13335
  if (style || width) {
@@ -13308,12 +13400,12 @@ function buildDisclosures(detected, css, cappedOut, unrepresentativeClassNames)
13308
13400
  return out;
13309
13401
  }
13310
13402
  function outPathIsGitIgnored(outPath) {
13311
- const dir = path38.dirname(outPath);
13403
+ const dir = path39.dirname(outPath);
13312
13404
  try {
13313
- const ignoreFile = path38.join(path38.dirname(dir), ".gitignore");
13405
+ const ignoreFile = path39.join(path39.dirname(dir), ".gitignore");
13314
13406
  if (!fs3.existsSync(ignoreFile)) return false;
13315
13407
  const patterns = fs3.readFileSync(ignoreFile, "utf8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
13316
- const base = path38.basename(dir);
13408
+ const base = path39.basename(dir);
13317
13409
  return patterns.some((p) => p === base || p === `${base}/` || p === `/${base}` || p === `/${base}/`);
13318
13410
  } catch {
13319
13411
  return false;
@@ -13460,8 +13552,8 @@ __export(profile_exports, {
13460
13552
  PROFILE_DESCRIPTION: () => PROFILE_DESCRIPTION,
13461
13553
  runProfile: () => runProfile
13462
13554
  });
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";
13555
+ import { closeSync, constants, existsSync as existsSync31, mkdirSync as mkdirSync9, openSync, realpathSync as realpathSync4, writeFileSync as writeFileSync13 } from "node:fs";
13556
+ import path40 from "node:path";
13465
13557
  function escapesScanRoot(outPath, scanRoot) {
13466
13558
  const resolveExisting = (target) => {
13467
13559
  let cursor = target;
@@ -13469,23 +13561,23 @@ function escapesScanRoot(outPath, scanRoot) {
13469
13561
  try {
13470
13562
  return realpathSync4(cursor);
13471
13563
  } catch {
13472
- const parent = path39.dirname(cursor);
13564
+ const parent = path40.dirname(cursor);
13473
13565
  if (parent === cursor) return cursor;
13474
13566
  cursor = parent;
13475
13567
  }
13476
13568
  }
13477
13569
  };
13478
13570
  const root = resolveExisting(scanRoot);
13479
- const dir = resolveExisting(path39.dirname(outPath));
13480
- return dir !== root && !dir.startsWith(root + path39.sep);
13571
+ const dir = resolveExisting(path40.dirname(outPath));
13572
+ return dir !== root && !dir.startsWith(root + path40.sep);
13481
13573
  }
13482
13574
  function runProfile(options) {
13483
13575
  if (options.describe) {
13484
13576
  printDescription(PROFILE_DESCRIPTION);
13485
13577
  return;
13486
13578
  }
13487
- const dir = path39.resolve(options.dir ?? ".");
13488
- if (!existsSync30(dir)) {
13579
+ const dir = path40.resolve(options.dir ?? ".");
13580
+ if (!existsSync31(dir)) {
13489
13581
  fail(options, ExitCode.InputValidation, {
13490
13582
  error: `no such directory: ${dir}`,
13491
13583
  code: "profile_dir_missing",
@@ -13493,7 +13585,7 @@ function runProfile(options) {
13493
13585
  });
13494
13586
  }
13495
13587
  const profile = scanCodebase({ roots: [dir], ...options.now ? { now: options.now } : {} });
13496
- const outPath = path39.resolve(options.out ?? path39.join(dir, "tendril-out", "codebase-profile.json"));
13588
+ const outPath = path40.resolve(options.out ?? path40.join(dir, "tendril-out", "codebase-profile.json"));
13497
13589
  if (!options.dryRun) {
13498
13590
  if (options.out === void 0 && escapesScanRoot(outPath, dir)) {
13499
13591
  fail(options, ExitCode.InputValidation, {
@@ -13502,10 +13594,10 @@ function runProfile(options) {
13502
13594
  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`)}\`.`
13503
13595
  });
13504
13596
  }
13505
- mkdirSync9(path39.dirname(outPath), { recursive: true });
13597
+ mkdirSync9(path40.dirname(outPath), { recursive: true });
13506
13598
  const handle = openSync(outPath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 420);
13507
13599
  try {
13508
- writeFileSync12(handle, `${JSON.stringify(profile, null, 2)}
13600
+ writeFileSync13(handle, `${JSON.stringify(profile, null, 2)}
13509
13601
  `, "utf8");
13510
13602
  } finally {
13511
13603
  closeSync(handle);
@@ -13567,7 +13659,7 @@ Written to ${outPath}
13567
13659
  `);
13568
13660
  if (!ignored) {
13569
13661
  process.stdout.write(
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.
13662
+ ` NOTE: ${path40.basename(path40.dirname(outPath))}/ is not gitignored here \u2014 add it to .gitignore, or this profile will show up in your next commit.
13571
13663
  `
13572
13664
  );
13573
13665
  }
@@ -13703,14 +13795,28 @@ var init_activate = __esm({
13703
13795
  var compose_exports = {};
13704
13796
  __export(compose_exports, {
13705
13797
  COMPOSE_DESCRIPTION: () => COMPOSE_DESCRIPTION,
13798
+ IMPLICIT_PARENT_SCAN_MAX_ENTRIES: () => IMPLICIT_PARENT_SCAN_MAX_ENTRIES,
13706
13799
  compositionPairsFor: () => compositionPairsFor,
13707
13800
  runCompose: () => runCompose
13708
13801
  });
13709
13802
  import { createHash as createHash8 } from "node:crypto";
13710
- import { existsSync as existsSync31, readFileSync as readFileSync28 } from "node:fs";
13711
- import path40 from "node:path";
13803
+ import { existsSync as existsSync32, readFileSync as readFileSync29, readdirSync as readdirSync14 } from "node:fs";
13804
+ import path41 from "node:path";
13712
13805
  function compositionPairsFor(hostSet, roots) {
13713
- const scanRoots = [.../* @__PURE__ */ new Set([...roots, path40.dirname(hostSet)])];
13806
+ const parent = path41.dirname(hostSet);
13807
+ const explicitRoots = [...new Set(roots)];
13808
+ let skippedParent;
13809
+ let parentRoot = [];
13810
+ if (!explicitRoots.some((r) => path41.resolve(r) === path41.resolve(parent))) {
13811
+ let parentEntries = 0;
13812
+ try {
13813
+ parentEntries = readdirSync14(parent).length;
13814
+ } catch {
13815
+ }
13816
+ if (parentEntries > IMPLICIT_PARENT_SCAN_MAX_ENTRIES) skippedParent = { dir: parent, entries: parentEntries };
13817
+ else parentRoot = [parent];
13818
+ }
13819
+ const scanRoots = [.../* @__PURE__ */ new Set([...explicitRoots, ...parentRoot])];
13714
13820
  const edges = composeReport(buildComposeIndex(scanRoots));
13715
13821
  const pairs = substitutionPairs(edges, hostSet);
13716
13822
  const { raw } = readManifestFile(hostSet);
@@ -13723,7 +13829,7 @@ function compositionPairsFor(hostSet, roots) {
13723
13829
  else invalid++;
13724
13830
  }
13725
13831
  const decidedKeys = new Set(standing.map((c) => fromStoredRel(c.partner.key)));
13726
- return { open: pairs.filter((p) => !decidedKeys.has(p.key)), standing, invalid };
13832
+ return { open: pairs.filter((p) => !decidedKeys.has(p.key)), standing, invalid, ...skippedParent !== void 0 ? { skippedParent } : {} };
13727
13833
  }
13728
13834
  function substitutionPairs(edges, hostSet) {
13729
13835
  const pairs = /* @__PURE__ */ new Map();
@@ -13742,7 +13848,7 @@ function substitutionPairs(edges, hostSet) {
13742
13848
  });
13743
13849
  }
13744
13850
  const pair = pairs.get(key);
13745
- const poseDisplay = e.pose.reps.map((r) => `${path40.basename(r.dir)}:${r.slug}`).join(", ");
13851
+ const poseDisplay = e.pose.reps.map((r) => `${path41.basename(r.dir)}:${r.slug}`).join(", ");
13746
13852
  pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
13747
13853
  for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
13748
13854
  }
@@ -13754,7 +13860,7 @@ function runCompose(flags) {
13754
13860
  return;
13755
13861
  }
13756
13862
  const base = process.env["INIT_CWD"] ?? process.cwd();
13757
- const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path40.resolve(base, d)) : [base];
13863
+ const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path41.resolve(base, d)) : [base];
13758
13864
  if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
13759
13865
  fail(flags, ExitCode.InputValidation, {
13760
13866
  error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
@@ -13763,7 +13869,7 @@ function runCompose(flags) {
13763
13869
  });
13764
13870
  }
13765
13871
  if (flags.set !== void 0) {
13766
- runComposeConfirm(flags, path40.resolve(base, flags.set), roots);
13872
+ runComposeConfirm(flags, path41.resolve(base, flags.set), roots);
13767
13873
  return;
13768
13874
  }
13769
13875
  const index = buildComposeIndex(roots);
@@ -13781,7 +13887,7 @@ function runCompose(flags) {
13781
13887
  }
13782
13888
  let lastHost = "";
13783
13889
  for (const e of edges) {
13784
- const host = `${path40.basename(e.hostSet)}`;
13890
+ const host = `${path41.basename(e.hostSet)}`;
13785
13891
  if (host !== lastHost) {
13786
13892
  process.stdout.write(`
13787
13893
  ${host}
@@ -13789,7 +13895,7 @@ ${host}
13789
13895
  lastHost = host;
13790
13896
  }
13791
13897
  const partners = e.partners.map((p) => p.displayName).join(" / ") || "\u2014";
13792
- const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path40.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
13898
+ const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path41.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
13793
13899
  process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
13794
13900
  `);
13795
13901
  for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
@@ -13801,14 +13907,14 @@ ${NOTE}
13801
13907
  });
13802
13908
  }
13803
13909
  function runComposeConfirm(flags, hostSet, roots) {
13804
- if (!existsSync31(path40.join(hostSet, "recording-set.json"))) {
13910
+ if (!existsSync32(path41.join(hostSet, "recording-set.json"))) {
13805
13911
  fail(flags, ExitCode.InputValidation, {
13806
13912
  error: `no recording-set.json in ${hostSet}`,
13807
13913
  code: "no-recording-set",
13808
13914
  remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
13809
13915
  });
13810
13916
  }
13811
- const scanRoots = [.../* @__PURE__ */ new Set([...roots, path40.dirname(hostSet)])];
13917
+ const scanRoots = [.../* @__PURE__ */ new Set([...roots, path41.dirname(hostSet)])];
13812
13918
  const index = buildComposeIndex(scanRoots);
13813
13919
  const edges = composeReport(index);
13814
13920
  const pairs = substitutionPairs(edges, hostSet);
@@ -13891,7 +13997,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
13891
13997
  // full recording-set hash join lands with pin authoring, where
13892
13998
  // task configs exist.)
13893
13999
  manifestSha256: Object.fromEntries(
13894
- p.partnerDirs.map((d) => [path40.relative(hostSet, d), createHash8("sha256").update(readFileSync28(path40.join(d, "recording-set.json"))).digest("hex")])
14000
+ p.partnerDirs.map((d) => [path41.relative(hostSet, d), createHash8("sha256").update(readFileSync29(path41.join(d, "recording-set.json"))).digest("hex")])
13895
14001
  )
13896
14002
  },
13897
14003
  // Schema shape ONLY (review: the display-side poseDisplay field
@@ -13915,7 +14021,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
13915
14021
  `);
13916
14022
  });
13917
14023
  }
13918
- var COMPOSE_DESCRIPTION, NOTE;
14024
+ var COMPOSE_DESCRIPTION, NOTE, IMPLICIT_PARENT_SCAN_MAX_ENTRIES;
13919
14025
  var init_compose2 = __esm({
13920
14026
  "packages/cli/src/commands/compose.ts"() {
13921
14027
  "use strict";
@@ -13947,6 +14053,7 @@ var init_compose2 = __esm({
13947
14053
  examples: ["tendril compose --list", "tendril compose --list --library ./recordings --json", "tendril compose --set ./recordings/dialog --confirm-compositions"]
13948
14054
  };
13949
14055
  NOTE = "Discovery only: these are PROPOSALS under the audited join rule (id evidence decides; name evidence only proposes). Confirm a pair with `compose --set <host>` (human-only, in your own terminal) and generation composes it: the partner's module ships under composed/ and the host imports it. Verified at that point is MODULE IDENTITY (pinned bytes + declared import) \u2014 not that the host renders it, and not pixel-neutrality: instance overrides are measured-real and a per-region check is still future work.";
14056
+ IMPLICIT_PARENT_SCAN_MAX_ENTRIES = 64;
13950
14057
  }
13951
14058
  });
13952
14059
 
@@ -13970,10 +14077,10 @@ __export(record_exports, {
13970
14077
  runRecordPlan: () => runRecordPlan,
13971
14078
  runRecordStatus: () => runRecordStatus
13972
14079
  });
13973
- import { existsSync as existsSync32, mkdtempSync as mkdtempSync2, readFileSync as readFileSync29, readdirSync as readdirSync14 } from "node:fs";
14080
+ import { existsSync as existsSync33, mkdtempSync as mkdtempSync2, readFileSync as readFileSync30, readdirSync as readdirSync15 } from "node:fs";
13974
14081
  import os7 from "node:os";
13975
- import path41 from "node:path";
13976
- import { writeFileSync as writeFileSync13 } from "node:fs";
14082
+ import path42 from "node:path";
14083
+ import { writeFileSync as writeFileSync14 } from "node:fs";
13977
14084
  function recordsInteractionState(reports) {
13978
14085
  const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
13979
14086
  return reports.some((r) => evident(r.axis) || r.domain.some(evident));
@@ -13995,7 +14102,7 @@ function interactionDisclosure(component, reports) {
13995
14102
  };
13996
14103
  }
13997
14104
  function symbolsFromMetadataEnvelope(file, sourceFrame) {
13998
- const env = JSON.parse(readFileSync29(file, "utf8"));
14105
+ const env = JSON.parse(readFileSync30(file, "utf8"));
13999
14106
  const text = env.content.map((c) => c.text ?? "").join("\n");
14000
14107
  const symbols = [];
14001
14108
  const walk2 = (node, ancestor) => {
@@ -14053,8 +14160,8 @@ function runRecordPlan(opts) {
14053
14160
  if (rawFile !== void 0) {
14054
14161
  try {
14055
14162
  const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
14056
- const tmp = path41.join(mkdtempSync2(path41.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
14057
- writeFileSync13(tmp, JSON.stringify(envelope));
14163
+ const tmp = path42.join(mkdtempSync2(path42.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
14164
+ writeFileSync14(tmp, JSON.stringify(envelope));
14058
14165
  metadataEntries.push({ file: tmp });
14059
14166
  } catch (err) {
14060
14167
  fail(opts, ExitCode.InputValidation, {
@@ -14075,7 +14182,7 @@ function runRecordPlan(opts) {
14075
14182
  let metadataTruncated = false;
14076
14183
  for (const { file, frame } of metadataEntries) {
14077
14184
  try {
14078
- const parsed = symbolsFromMetadataEnvelope(path41.resolve(file), frame);
14185
+ const parsed = symbolsFromMetadataEnvelope(path42.resolve(file), frame);
14079
14186
  symbols.push(...parsed.symbols);
14080
14187
  if (parsed.truncated) metadataTruncated = true;
14081
14188
  } catch (err) {
@@ -14109,7 +14216,7 @@ function runRecordPlan(opts) {
14109
14216
  if (symbols.length === 0) {
14110
14217
  const leads = metadataEntries.flatMap(({ file }) => {
14111
14218
  try {
14112
- const env = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
14219
+ const env = JSON.parse(readFileSync30(path42.resolve(file), "utf8"));
14113
14220
  return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
14114
14221
  } catch {
14115
14222
  return [];
@@ -14202,7 +14309,7 @@ function runRecordPlan(opts) {
14202
14309
  // a CLI flag and the MCP surface has no parameter for it.
14203
14310
  decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
14204
14311
  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.",
14205
- userRuns: [`rm ${quoteArg(path41.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
14312
+ userRuns: [`rm ${quoteArg(path42.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
14206
14313
  },
14207
14314
  {
14208
14315
  id: "larger-allowance",
@@ -14400,7 +14507,7 @@ function runRecordNext(opts) {
14400
14507
  const progress = payload["progress"];
14401
14508
  process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
14402
14509
  \u2192 ${payload["note"]}
14403
- \u2192 then: ${tendrilCommand(`record ingest --set ${path41.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
14510
+ \u2192 then: ${tendrilCommand(`record ingest --set ${path42.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
14404
14511
  `);
14405
14512
  });
14406
14513
  }
@@ -14474,7 +14581,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14474
14581
  const skipped = [];
14475
14582
  const failed = [];
14476
14583
  for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
14477
- if (existsSync32(path41.join(setDir, rep, name))) {
14584
+ if (existsSync33(path42.join(setDir, rep, name))) {
14478
14585
  skipped.push(name);
14479
14586
  continue;
14480
14587
  }
@@ -14496,16 +14603,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14496
14603
  }
14497
14604
  function rawEnvelopeFromFile(file, parts) {
14498
14605
  if (parts) {
14499
- const blocks = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
14606
+ const blocks = JSON.parse(readFileSync30(path42.resolve(file), "utf8"));
14500
14607
  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");
14501
14608
  return { content: blocks.map((text) => ({ type: "text", text })) };
14502
14609
  }
14503
- return { content: [{ type: "text", text: readFileSync29(path41.resolve(file), "utf8") }] };
14610
+ return { content: [{ type: "text", text: readFileSync30(path42.resolve(file), "utf8") }] };
14504
14611
  }
14505
14612
  async function runRecordIngest(opts) {
14506
14613
  let payload;
14507
14614
  try {
14508
- payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync29(path41.resolve(opts.file), "utf8"));
14615
+ payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync30(path42.resolve(opts.file), "utf8"));
14509
14616
  } catch (err) {
14510
14617
  fail(opts, ExitCode.InputValidation, {
14511
14618
  error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
@@ -14517,7 +14624,7 @@ async function runRecordIngest(opts) {
14517
14624
  fail(opts, ExitCode.InputValidation, {
14518
14625
  error: "--raw is for text tool responses; screenshots are binary",
14519
14626
  code: "envelope-invalid",
14520
- remediation: `Use \`${tendrilCommand(`record fetch --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14627
+ remediation: `Use \`${tendrilCommand(`record fetch --set ${path42.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14521
14628
  });
14522
14629
  }
14523
14630
  if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
@@ -14537,7 +14644,7 @@ async function runRecordIngest(opts) {
14537
14644
  remediation: REINGEST_GUIDANCE
14538
14645
  });
14539
14646
  }
14540
- writeFileSync13(path41.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14647
+ writeFileSync14(path42.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14541
14648
  `);
14542
14649
  emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14543
14650
  process.stdout.write("set-level get_variable_defs ingested\n");
@@ -14561,7 +14668,7 @@ async function runRecordIngest(opts) {
14561
14668
  remediation: REINGEST_GUIDANCE
14562
14669
  });
14563
14670
  }
14564
- writeFileSync13(path41.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14671
+ writeFileSync14(path42.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14565
14672
  `);
14566
14673
  emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14567
14674
  process.stdout.write("set-level get_motion_context ingested\n");
@@ -14577,7 +14684,7 @@ async function runRecordIngest(opts) {
14577
14684
  if (assets !== void 0) {
14578
14685
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14579
14686
  `);
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>`)}
14687
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path42.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14581
14688
  `);
14582
14689
  }
14583
14690
  });
@@ -14650,15 +14757,15 @@ async function runRecordIngestRep(opts) {
14650
14757
  if (assets !== void 0) {
14651
14758
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14652
14759
  `);
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>`)}
14760
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path42.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14654
14761
  `);
14655
14762
  }
14656
14763
  });
14657
14764
  }
14658
14765
  function runRecordAsset(opts) {
14659
14766
  if (opts.dir !== void 0) {
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));
14767
+ const dir = path42.resolve(opts.dir);
14768
+ const names = readdirSync15(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
14662
14769
  if (names.length === 0) {
14663
14770
  fail(opts, ExitCode.InputValidation, {
14664
14771
  error: `no asset-*.<ext> files found in ${dir}`,
@@ -14669,7 +14776,7 @@ function runRecordAsset(opts) {
14669
14776
  const ingested = [];
14670
14777
  try {
14671
14778
  for (const name of names) {
14672
- ingestAsset(opts.setDir, opts.rep, name, readFileSync29(path41.join(dir, name)));
14779
+ ingestAsset(opts.setDir, opts.rep, name, readFileSync30(path42.join(dir, name)));
14673
14780
  ingested.push(name);
14674
14781
  }
14675
14782
  } catch (err) {
@@ -14689,11 +14796,11 @@ function runRecordAsset(opts) {
14689
14796
  fail(opts, ExitCode.InputValidation, {
14690
14797
  error: "pass --name and --file for a single asset, or --dir for a batch",
14691
14798
  code: "asset-rejected",
14692
- remediation: tendrilCommand(`record asset --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14799
+ remediation: tendrilCommand(`record asset --set ${path42.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14693
14800
  });
14694
14801
  }
14695
14802
  try {
14696
- ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync29(path41.resolve(opts.file)));
14803
+ ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync30(path42.resolve(opts.file)));
14697
14804
  emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
14698
14805
  process.stdout.write(`ingested ${opts.rep}/${opts.name}
14699
14806
  `);
@@ -14710,8 +14817,8 @@ function runRecordStatus(opts) {
14710
14817
  const status = sessionStatus(opts.setDir);
14711
14818
  const composition = (() => {
14712
14819
  try {
14713
- const setDir = path41.resolve(opts.setDir);
14714
- const { open, standing, invalid } = compositionPairsFor(setDir, [path41.dirname(setDir)]);
14820
+ const setDir = path42.resolve(opts.setDir);
14821
+ const { open, standing, invalid } = compositionPairsFor(setDir, [path42.dirname(setDir)]);
14715
14822
  return {
14716
14823
  openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
14717
14824
  confirmed: standing.filter((s) => s.status === "confirmed").length,
@@ -14732,7 +14839,7 @@ function runRecordStatus(opts) {
14732
14839
  }
14733
14840
  }
14734
14841
  process.stdout.write(
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\`
14842
+ status.motion.recorded ? motionTruthFor(path42.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\`
14736
14843
  ` : "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"
14737
14844
  );
14738
14845
  if ("unavailable" in composition) {
@@ -14740,7 +14847,7 @@ function runRecordStatus(opts) {
14740
14847
  `);
14741
14848
  } else if (composition.openPairs.length > 0) {
14742
14849
  process.stdout.write(
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.
14850
+ `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 ${path42.resolve(opts.setDir)}`)}\` lists it; confirmation is human-only.
14744
14851
  `
14745
14852
  );
14746
14853
  } else if (composition.confirmed > 0) {
@@ -14780,7 +14887,7 @@ function narrowedRoles(derived, override) {
14780
14887
  function rolesFromFile(opts, file, derived) {
14781
14888
  let json;
14782
14889
  try {
14783
- json = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
14890
+ json = JSON.parse(readFileSync30(path42.resolve(file), "utf8"));
14784
14891
  } catch (err) {
14785
14892
  fail(opts, ExitCode.InputValidation, {
14786
14893
  error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -14818,11 +14925,11 @@ function rolesFromFile(opts, file, derived) {
14818
14925
  };
14819
14926
  }
14820
14927
  function runRecordFinish(opts) {
14821
- if (!existsSync32(path41.join(opts.setDir, "recording-set.json"))) {
14928
+ if (!existsSync33(path42.join(opts.setDir, "recording-set.json"))) {
14822
14929
  fail(opts, ExitCode.InputValidation, {
14823
14930
  error: `no recording-set.json in ${opts.setDir}`,
14824
14931
  code: "no-recording-set",
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.`
14932
+ remediation: `Run \`${tendrilCommand(`record plan --set ${path42.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
14826
14933
  });
14827
14934
  }
14828
14935
  const { manifest, raw } = readManifestFile(opts.setDir);
@@ -14850,17 +14957,17 @@ function runRecordFinish(opts) {
14850
14957
  fail(opts, ExitCode.ConfirmationRequired, {
14851
14958
  error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
14852
14959
  code: "roles-confirmation-not-interactive",
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.`
14960
+ remediation: `A human runs \`${tendrilCommand(`record finish --set ${path42.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
14854
14961
  });
14855
14962
  }
14856
14963
  const merged = { ...raw, roles };
14857
- const { issues } = validateRecordingSet(merged, (rel) => existsSync32(path41.join(opts.setDir, rel)));
14964
+ const { issues } = validateRecordingSet(merged, (rel) => existsSync33(path42.join(opts.setDir, rel)));
14858
14965
  const errors = issues.filter((i) => i.severity === "error");
14859
14966
  if (errors.length > 0) {
14860
14967
  fail(opts, ExitCode.InputValidation, {
14861
14968
  error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
14862
14969
  code: "recording-set-invalid",
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\`.`
14970
+ remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path42.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
14864
14971
  });
14865
14972
  }
14866
14973
  for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
@@ -14910,9 +15017,9 @@ var init_record = __esm({
14910
15017
  });
14911
15018
 
14912
15019
  // packages/cli/src/font-guidance.ts
14913
- import path42 from "node:path";
15020
+ import path43 from "node:path";
14914
15021
  function fontsUnprovenRemediation(setDir) {
14915
- const set = setDir === void 0 ? void 0 : path42.resolve(setDir);
15022
+ const set = setDir === void 0 ? void 0 : path43.resolve(setDir);
14916
15023
  if (set !== void 0) {
14917
15024
  try {
14918
15025
  const needs = recordedFontNeeds(set);
@@ -14987,8 +15094,8 @@ __export(fonts_exports, {
14987
15094
  runFontsResolveSet: () => runFontsResolveSet,
14988
15095
  runFontsStatus: () => runFontsStatus
14989
15096
  });
14990
- import { existsSync as existsSync33, readFileSync as readFileSync30 } from "node:fs";
14991
- import path43 from "node:path";
15097
+ import { existsSync as existsSync34, readFileSync as readFileSync31 } from "node:fs";
15098
+ import path44 from "node:path";
14992
15099
  async function runFontsResolve(opts) {
14993
15100
  const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
14994
15101
  const queryRefusal = systemFaceRefusal(opts.family.trim());
@@ -15009,7 +15116,7 @@ async function runFontsResolve(opts) {
15009
15116
  }
15010
15117
  }
15011
15118
  async function runFontsResolveSet(opts) {
15012
- const setDir = path43.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
15119
+ const setDir = path44.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
15013
15120
  let needs = [];
15014
15121
  try {
15015
15122
  needs = recordedFontNeeds(setDir);
@@ -15104,16 +15211,16 @@ async function runFontsResolveSet(opts) {
15104
15211
  }
15105
15212
  }
15106
15213
  function runFontsStatus(opts) {
15107
- const manifestPath2 = path43.join(opts.cacheDir, "manifest.json");
15108
- if (!existsSync33(manifestPath2)) {
15214
+ const manifestPath2 = path44.join(opts.cacheDir, "manifest.json");
15215
+ if (!existsSync34(manifestPath2)) {
15109
15216
  fail(opts, ExitCode.FontsUnproven, {
15110
15217
  error: `no font cache at ${opts.cacheDir}`,
15111
15218
  code: "fonts-unresolved",
15112
15219
  remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
15113
15220
  });
15114
15221
  }
15115
- const faces = JSON.parse(readFileSync30(manifestPath2, "utf8"));
15116
- const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path43.resolve(opts.lock), opts.cacheDir) : null;
15222
+ const faces = JSON.parse(readFileSync31(manifestPath2, "utf8"));
15223
+ const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path44.resolve(opts.lock), opts.cacheDir) : null;
15117
15224
  emitData(opts, { faces, lockVerdicts }, () => {
15118
15225
  for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
15119
15226
  `);
@@ -15157,13 +15264,13 @@ function familyMismatch(family, declared) {
15157
15264
  }
15158
15265
  function runFontsAdd(opts) {
15159
15266
  if (opts.set !== void 0) {
15160
- const declared = taskFontFamilies(path43.resolve(opts.set)) ?? [];
15267
+ const declared = taskFontFamilies(path44.resolve(opts.set)) ?? [];
15161
15268
  const mismatch = familyMismatch(opts.family, declared);
15162
15269
  if (mismatch !== void 0) {
15163
15270
  fail(opts, ExitCode.InputValidation, {
15164
15271
  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.`,
15165
15272
  code: "font-family-not-declared",
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.`
15273
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path44.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.`
15167
15274
  });
15168
15275
  }
15169
15276
  } else {
@@ -15222,13 +15329,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
15222
15329
  }
15223
15330
  function runFontsAddSystem(opts) {
15224
15331
  if (opts.set !== void 0) {
15225
- const declared = taskFontFamilies(path43.resolve(opts.set)) ?? [];
15332
+ const declared = taskFontFamilies(path44.resolve(opts.set)) ?? [];
15226
15333
  const mismatch = familyMismatch(opts.family, declared);
15227
15334
  if (mismatch !== void 0) {
15228
15335
  fail(opts, ExitCode.InputValidation, {
15229
15336
  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.`,
15230
15337
  code: "font-family-not-declared",
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.`
15338
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path44.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
15232
15339
  });
15233
15340
  }
15234
15341
  } else {
@@ -15278,12 +15385,12 @@ var init_fonts = __esm({
15278
15385
  });
15279
15386
 
15280
15387
  // packages/cli/src/profile-input.ts
15281
- import { existsSync as existsSync34, readFileSync as readFileSync31 } from "node:fs";
15282
- import path44 from "node:path";
15388
+ import { existsSync as existsSync35, readFileSync as readFileSync32 } from "node:fs";
15389
+ import path45 from "node:path";
15283
15390
  function loadCodebaseProfile(flags, profilePath) {
15284
15391
  if (profilePath === void 0) return null;
15285
- const abs = path44.resolve(profilePath);
15286
- if (!existsSync34(abs)) {
15392
+ const abs = path45.resolve(profilePath);
15393
+ if (!existsSync35(abs)) {
15287
15394
  fail(flags, ExitCode.InputValidation, {
15288
15395
  error: `no profile at ${abs}`,
15289
15396
  code: "profile_missing",
@@ -15291,7 +15398,7 @@ function loadCodebaseProfile(flags, profilePath) {
15291
15398
  });
15292
15399
  }
15293
15400
  try {
15294
- return readCodebaseProfile(readFileSync31(abs, "utf8"));
15401
+ return readCodebaseProfile(readFileSync32(abs, "utf8"));
15295
15402
  } catch (error) {
15296
15403
  fail(flags, ExitCode.InputValidation, {
15297
15404
  error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
@@ -15333,8 +15440,8 @@ __export(verify_exports, {
15333
15440
  runVerify: () => runVerify,
15334
15441
  verdictCaveatsFor: () => verdictCaveatsFor
15335
15442
  });
15336
- import { existsSync as existsSync35, readFileSync as readFileSync32, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
15337
- import path45 from "node:path";
15443
+ import { existsSync as existsSync36, readFileSync as readFileSync33, rmSync as rmSync5, writeFileSync as writeFileSync15 } from "node:fs";
15444
+ import path46 from "node:path";
15338
15445
  function interactionCoverage(behaviors) {
15339
15446
  const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
15340
15447
  const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
@@ -15575,12 +15682,12 @@ function compositionReport(input) {
15575
15682
  function eyeCheck(bundleDir) {
15576
15683
  return {
15577
15684
  command: tendrilCommand(`inspect "${bundleDir}"`),
15578
- sheetPath: path45.join(bundleDir, "verify-evidence", "inspect.html"),
15685
+ sheetPath: path46.join(bundleDir, "verify-evidence", "inspect.html"),
15579
15686
  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."
15580
15687
  };
15581
15688
  }
15582
15689
  function evidenceArtifacts(evidenceDir, reps) {
15583
- const named = (name) => existsSync35(path45.join(evidenceDir, name)) ? name : null;
15690
+ const named = (name) => existsSync36(path46.join(evidenceDir, name)) ? name : null;
15584
15691
  return {
15585
15692
  legend: named("diff-legend.txt"),
15586
15693
  configs: reps.map((rep) => {
@@ -15628,7 +15735,7 @@ function taskFromManifest(opts, manifest, setDir) {
15628
15735
  const adapterSlugs = Object.keys(manifest.propAdapter);
15629
15736
  const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
15630
15737
  const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
15631
- const registry = Object.values(TASKS).find((t) => path45.resolve(t.set) === path45.resolve(setDir));
15738
+ const registry = Object.values(TASKS).find((t) => path46.resolve(t.set) === path46.resolve(setDir));
15632
15739
  const authored = (() => {
15633
15740
  if (registry !== void 0) return void 0;
15634
15741
  try {
@@ -15682,25 +15789,26 @@ function verdictCaveatsFor(input) {
15682
15789
  ...input.operability !== "verified" ? ["operability-unverified"] : [],
15683
15790
  ...(input.pixelOnlyInteractionPoses ?? 0) > 0 ? ["interaction-poses-unchecked"] : [],
15684
15791
  ...input.compositionUnavailable ? ["composition-not-checked"] : [],
15685
- ...input.fontsSubstituted ? ["fonts-substituted"] : []
15792
+ ...input.fontsSubstituted ? ["fonts-substituted"] : [],
15793
+ ...input.hoverBudgetNondefault === true ? ["hover-budget-nondefault"] : []
15686
15794
  ];
15687
15795
  }
15688
15796
  async function runVerify(opts) {
15689
15797
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
15690
15798
  let recordingSetDrift;
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)) {
15799
+ const setOverride = opts.set !== void 0 ? path46.resolve(callerCwd, opts.set) : void 0;
15800
+ opts = { ...opts, bundleDir: path46.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
15801
+ if (!existsSync36(opts.bundleDir)) {
15694
15802
  fail(opts, ExitCode.InputValidation, {
15695
15803
  error: `bundle directory not found: ${opts.bundleDir}`,
15696
15804
  code: "bundle-missing",
15697
15805
  remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
15698
15806
  });
15699
15807
  }
15700
- const manifestPath2 = path45.join(opts.bundleDir, "component.json");
15808
+ const manifestPath2 = path46.join(opts.bundleDir, "component.json");
15701
15809
  let manifest;
15702
- if (existsSync35(manifestPath2)) {
15703
- const { manifest: parsed, issues } = readBundleManifest(readFileSync32(manifestPath2, "utf8"));
15810
+ if (existsSync36(manifestPath2)) {
15811
+ const { manifest: parsed, issues } = readBundleManifest(readFileSync33(manifestPath2, "utf8"));
15704
15812
  if (issues.length > 0) {
15705
15813
  fail(opts, ExitCode.InputValidation, {
15706
15814
  error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15731,21 +15839,21 @@ async function runVerify(opts) {
15731
15839
  task = registry;
15732
15840
  } else if (manifest !== void 0) {
15733
15841
  const resolveSetDir = (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);
15842
+ if (path46.isAbsolute(p)) return p;
15843
+ const fromRepo = path46.resolve(REPO_ROOT, p);
15844
+ if (existsSync36(fromRepo)) return fromRepo;
15845
+ return path46.resolve(callerCwd, p);
15738
15846
  };
15739
15847
  const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
15740
- if (!existsSync35(path45.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path45.resolve(t.set) === path45.resolve(setDir))) {
15848
+ if (!existsSync36(path46.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path46.resolve(t.set) === path46.resolve(setDir))) {
15741
15849
  fail(opts, ExitCode.RecordingIncomplete, {
15742
15850
  error: `recording set not found or unmanifested: ${setDir}`,
15743
15851
  code: "recording-set-missing",
15744
15852
  remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
15745
15853
  });
15746
15854
  }
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"))) {
15855
+ const registry = Object.values(TASKS).find((t) => path46.resolve(t.set) === path46.resolve(setDir));
15856
+ if (registry !== void 0 && !existsSync36(path46.join(setDir, "recording-set.json"))) {
15749
15857
  const recordedSlugs = registry.configs.map((c) => c.rep);
15750
15858
  const adapterSlugs = Object.keys(manifest.propAdapter);
15751
15859
  unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
@@ -15777,9 +15885,9 @@ async function runVerify(opts) {
15777
15885
  recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
15778
15886
  }
15779
15887
  for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
15780
- const p = path45.join(opts.bundleDir, name);
15781
- if (!existsSync35(p)) continue;
15782
- const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync32(p)));
15888
+ const p = path46.join(opts.bundleDir, name);
15889
+ if (!existsSync36(p)) continue;
15890
+ const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync33(p)));
15783
15891
  if (issues.length > 0) {
15784
15892
  fail(opts, ExitCode.InputValidation, {
15785
15893
  error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15817,7 +15925,7 @@ async function runVerify(opts) {
15817
15925
  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)`);
15818
15926
  }
15819
15927
  const missing = task.configs.filter(
15820
- (c) => !existsSync35(path45.join(task.set, c.rep, "get_screenshot.json")) || !existsSync35(path45.join(task.set, c.rep, "get_metadata.json"))
15928
+ (c) => !existsSync36(path46.join(task.set, c.rep, "get_screenshot.json")) || !existsSync36(path46.join(task.set, c.rep, "get_metadata.json"))
15821
15929
  );
15822
15930
  if (missing.length > 0) {
15823
15931
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -15827,7 +15935,7 @@ async function runVerify(opts) {
15827
15935
  });
15828
15936
  }
15829
15937
  const bar = BARS2[opts.bar];
15830
- const evidenceDir = path45.join(opts.bundleDir, "verify-evidence");
15938
+ const evidenceDir = path46.join(opts.bundleDir, "verify-evidence");
15831
15939
  rmSync5(evidenceDir, { recursive: true, force: true });
15832
15940
  const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
15833
15941
  const quality = await checkBundleQuality(
@@ -15845,14 +15953,19 @@ async function runVerify(opts) {
15845
15953
  // ASKED, never "follows every convention".
15846
15954
  loadCodebaseProfile(opts, opts.profile) ?? void 0
15847
15955
  );
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");
15956
+ const bundleCss = ["tokens.css", "styles.css"].map((f) => path46.join(opts.bundleDir, f)).filter((f) => existsSync36(f)).map((f) => readFileSync33(f, "utf8")).join("\n");
15849
15957
  const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
15850
- const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs);
15958
+ const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs, {
15959
+ ...opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {},
15960
+ // Run-27 diagnostics: a mount failure's screenshot lands beside the
15961
+ // rest of the evidence (cleared with it on every fresh run).
15962
+ failureShotDir: evidenceDir
15963
+ });
15851
15964
  const framing = checkAdapterFraming(
15852
15965
  task.configs,
15853
15966
  adapterVocabulary ?? buildAdapterVocabulary({ authorityConfigs: authorityConfigs ?? task.configs })
15854
15967
  );
15855
- const behaviors = [...await checkBehaviors(task, opts.bundleDir), ...parity];
15968
+ const behaviors = [...await checkBehaviors(task, opts.bundleDir, opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {}), ...parity];
15856
15969
  const structural = roles !== void 0 ? await checkStructuralComposition(task, opts.bundleDir, roles) : [];
15857
15970
  const regionsOut = roles !== void 0 ? interiorRegions(task.set, roles) : void 0;
15858
15971
  const crops = regionsOut !== void 0 && "regions" in regionsOut ? await checkCropComposition(task, opts.bundleDir, regionsOut.regions) : void 0;
@@ -15861,10 +15974,10 @@ async function runVerify(opts) {
15861
15974
  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.`);
15862
15975
  }
15863
15976
  const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
15864
- const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path45.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
15977
+ const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path46.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
15865
15978
  const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
15866
15979
  const renderExpectations = verifyPins.pins.map((pin) => ({
15867
- modulePath: path45.join(composedModuleDir(pin.partnerName), pin.entryComponent),
15980
+ modulePath: path46.join(composedModuleDir(pin.partnerName), pin.entryComponent),
15868
15981
  component: pin.entryComponent,
15869
15982
  stampName: `${pin.pairKey}:${pin.entryComponent}`,
15870
15983
  hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
@@ -15932,7 +16045,11 @@ async function runVerify(opts) {
15932
16045
  // Canonical mount semantics are VERSION-scoped (0.1.22 review: the
15933
16046
  // greyscale flip changed Windows pixels under an identical-looking
15934
16047
  // stamp) — the ruler version is part of the comparability key.
15935
- environment: { ...environmentStamp(taskFontFamilies(task.set)), ruler: cliVersion() },
16048
+ // The hover budget is part of how the behavior numbers were
16049
+ // produced (run 27), so it travels with the environment — always,
16050
+ // not only when overridden, so a reader never has to know the
16051
+ // default to interpret a report.
16052
+ environment: { ...environmentStamp(taskFontFamilies(task.set)), ruler: cliVersion(), hoverBudgetMs: opts.hoverTimeoutMs ?? DEFAULT_HOVER_BUDGET_MS },
15936
16053
  coverage: {
15937
16054
  scoredConfigs: statuses.length,
15938
16055
  certified,
@@ -16006,7 +16123,8 @@ async function runVerify(opts) {
16006
16123
  operability: coverage.operability,
16007
16124
  compositionUnavailable: "unavailable" in compositionBlock,
16008
16125
  fontsSubstituted: substitutedFamilies.length > 0,
16009
- pixelOnlyInteractionPoses: unmappedInteractionEvidence.length
16126
+ pixelOnlyInteractionPoses: unmappedInteractionEvidence.length,
16127
+ hoverBudgetNondefault: opts.hoverTimeoutMs !== void 0 && opts.hoverTimeoutMs !== DEFAULT_HOVER_BUDGET_MS
16010
16128
  }),
16011
16129
  // Machine-readable cause (adversarial review): a cert-bar failure
16012
16130
  // with zero pixel/behavior/composition failures was only
@@ -16025,8 +16143,9 @@ async function runVerify(opts) {
16025
16143
  emitData(opts, report, () => {
16026
16144
  for (const s of statuses) {
16027
16145
  const demoted = "demotedBy" in s && Array.isArray(s.demotedBy) ? ` [demoted: ${s.demotedBy.join("; ")}]` : "";
16146
+ const pixels = tierOf(s, BARS2.cert).toUpperCase();
16028
16147
  process.stdout.write(
16029
- `${s.status === "fail" ? "FAIL" : s.status.toUpperCase().padEnd(9)} ${s.rep.padEnd(24)} sim=${s.similarity} ink=${s.inkRecall}${s.error !== void 0 ? ` [${s.error}]` : ""}${demoted}
16148
+ `${(s.status === "fail" ? "FAIL" : s.status.toUpperCase()).padEnd(9)} ${`pixels=${pixels}`.padEnd(17)} ${s.rep.padEnd(24)} sim=${s.similarity} ink=${s.inkRecall}${s.error !== void 0 ? ` [${s.error}]` : ""}${demoted}
16030
16149
  `
16031
16150
  );
16032
16151
  }
@@ -16151,7 +16270,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16151
16270
  }
16152
16271
  process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
16153
16272
  `);
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")));
16273
+ const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path46.join(opts.bundleDir, f)).filter((f) => existsSync36(f)).map((f) => readFileSync33(f, "utf8")).join("\n")));
16155
16274
  if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
16156
16275
  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)
16157
16276
  `);
@@ -16206,7 +16325,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16206
16325
  persistReport(opts, report, evidenceDir);
16207
16326
  }
16208
16327
  function persistReport(opts, report, evidenceDir) {
16209
- if (!existsSync35(evidenceDir)) return;
16328
+ if (!existsSync36(evidenceDir)) return;
16210
16329
  const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
16211
16330
  const withExit = {
16212
16331
  ...report,
@@ -16214,9 +16333,9 @@ function persistReport(opts, report, evidenceDir) {
16214
16333
  ...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
16215
16334
  };
16216
16335
  try {
16217
- writeFileSync14(
16218
- path45.join(evidenceDir, VERIFY_REPORT_FILENAME),
16219
- `${JSON.stringify(verifyReportForTransport(withExit, path45.basename(opts.bundleDir)), null, 2)}
16336
+ writeFileSync15(
16337
+ path46.join(evidenceDir, VERIFY_REPORT_FILENAME),
16338
+ `${JSON.stringify(verifyReportForTransport(withExit, path46.basename(opts.bundleDir)), null, 2)}
16220
16339
  `
16221
16340
  );
16222
16341
  } catch (e) {
@@ -16266,11 +16385,11 @@ __export(engine_exports, {
16266
16385
  runEngineBrief: () => runEngineBrief,
16267
16386
  runEngineScore: () => runEngineScore
16268
16387
  });
16269
- import { appendFileSync, existsSync as existsSync36, mkdirSync as mkdirSync10, readFileSync as readFileSync33, writeFileSync as writeFileSync15 } from "node:fs";
16270
- import path46 from "node:path";
16388
+ import { appendFileSync, existsSync as existsSync37, mkdirSync as mkdirSync10, readFileSync as readFileSync34, writeFileSync as writeFileSync16 } from "node:fs";
16389
+ import path47 from "node:path";
16271
16390
  function resolveEngineTask(opts, callerCwd) {
16272
- const asPath = path46.resolve(callerCwd, opts.taskOrSet);
16273
- const isSet = existsSync36(path46.join(asPath, "recording-set.json"));
16391
+ const asPath = path47.resolve(callerCwd, opts.taskOrSet);
16392
+ const isSet = existsSync37(path47.join(asPath, "recording-set.json"));
16274
16393
  const registry = TASKS[opts.taskOrSet];
16275
16394
  if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
16276
16395
  if (isSet) {
@@ -16279,7 +16398,7 @@ function resolveEngineTask(opts, callerCwd) {
16279
16398
  for (const d of authored.disclosures) warn(opts, d);
16280
16399
  return {
16281
16400
  task: authored.task,
16282
- name: path46.basename(asPath),
16401
+ name: path47.basename(asPath),
16283
16402
  ref: asPath,
16284
16403
  disclosures: authored.disclosures,
16285
16404
  interactionEvidence: authored.api.interactionEvidence,
@@ -16307,13 +16426,18 @@ function runEngineBrief(opts) {
16307
16426
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16308
16427
  const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
16309
16428
  const bar = BARS3[opts.bar];
16310
- if (existsSync36(path46.join(task.set, "recording-set.json"))) {
16429
+ if (existsSync37(path47.join(task.set, "recording-set.json"))) {
16311
16430
  try {
16312
- const { open } = compositionPairsFor(path46.resolve(task.set), [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
16431
+ const { open, skippedParent } = compositionPairsFor(path47.resolve(task.set), [opts.library !== void 0 ? path47.resolve(callerCwd, opts.library) : callerCwd]);
16432
+ if (skippedParent !== void 0) {
16433
+ disclosures.push(
16434
+ `COMPOSITION DISCOVERY PARTIAL: the set's parent directory (${skippedParent.dir}) holds ${String(skippedParent.entries)} entries and was not scanned as a recordings library \u2014 sibling sets there are invisible to pairing. Pass --library <dir> to scan a specific library deliberately.`
16435
+ );
16436
+ }
16313
16437
  if (open.length > 0) {
16314
16438
  const componentNames = [...new Set(open.map((p) => p.displayName))];
16315
16439
  disclosures.push(
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.`
16440
+ `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 ${path47.resolve(task.set)}`)}\` in their terminal, and re-emit this brief. Proceeding as-is is valid; it just is not composition.`
16317
16441
  );
16318
16442
  }
16319
16443
  } catch (err) {
@@ -16330,9 +16454,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16330
16454
  const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
16331
16455
  const segments = buildSegments(task, "files");
16332
16456
  let notRecorded;
16333
- const manifestPath2 = path46.join(task.set, "recording-set.json");
16334
- if (existsSync36(manifestPath2)) {
16335
- notRecorded = JSON.parse(readFileSync33(manifestPath2, "utf8")).notRecorded;
16457
+ const manifestPath2 = path47.join(task.set, "recording-set.json");
16458
+ if (existsSync37(manifestPath2)) {
16459
+ notRecorded = JSON.parse(readFileSync34(manifestPath2, "utf8")).notRecorded;
16336
16460
  }
16337
16461
  const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
16338
16462
 
@@ -16340,7 +16464,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16340
16464
  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.
16341
16465
  ${notRecorded}` : "";
16342
16466
  let fontProvisioning;
16343
- if (existsSync36(manifestPath2)) {
16467
+ if (existsSync37(manifestPath2)) {
16344
16468
  const missingFams = unprovisionedFamilies(task.set);
16345
16469
  const unprovided = unprovisionedFaces(task.set);
16346
16470
  const weightOnly = missingFams.length === 0;
@@ -16362,7 +16486,7 @@ ${notRecorded}` : "";
16362
16486
  };
16363
16487
  }
16364
16488
  }
16365
- const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
16489
+ const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path47.resolve(callerCwd, opts.library) : callerCwd]);
16366
16490
  const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
16367
16491
  const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
16368
16492
 
@@ -16398,10 +16522,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
16398
16522
 
16399
16523
  === TASK PAYLOAD (recorded truth, verbatim) ===
16400
16524
  ${segments}`;
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);
16525
+ const payloadFile = path47.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
16526
+ const candidateDirSuggestion = path47.resolve(callerCwd, `tendril-out/${name}-candidate`);
16527
+ mkdirSync10(path47.dirname(payloadFile), { recursive: true });
16528
+ writeFileSync16(payloadFile, payload);
16405
16529
  emitData(
16406
16530
  opts,
16407
16531
  {
@@ -16447,7 +16571,7 @@ ${segments}`;
16447
16571
  // command must search the same bundle roots the pins came
16448
16572
  // from, or the oracle and the brief describe different worlds.
16449
16573
  `Run \`${tendrilCommand(
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`
16574
+ `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path47.resolve(callerCwd, opts.library))}` : ""} --json`
16451
16575
  )}\` \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.`,
16452
16576
  "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).",
16453
16577
  "Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
@@ -16462,8 +16586,8 @@ ${segments}`;
16462
16586
  );
16463
16587
  }
16464
16588
  function appendScoreHistory(candidateDir, entry) {
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;
16589
+ const file = path47.join(candidateDir, "score-history.jsonl");
16590
+ const starts = existsSync37(file) ? readFileSync34(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
16467
16591
  const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
16468
16592
  appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
16469
16593
  `);
@@ -16471,9 +16595,9 @@ function appendScoreHistory(candidateDir, entry) {
16471
16595
  async function runEngineScore(opts) {
16472
16596
  requireEntitlement(opts);
16473
16597
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16474
- const candidateDir = path46.resolve(callerCwd, opts.candidateDir);
16598
+ const candidateDir = path47.resolve(callerCwd, opts.candidateDir);
16475
16599
  const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
16476
- if (!existsSync36(candidateDir)) {
16600
+ if (!existsSync37(candidateDir)) {
16477
16601
  fail(opts, ExitCode.InputValidation, {
16478
16602
  error: `candidate directory not found: ${candidateDir}`,
16479
16603
  code: "candidate-missing",
@@ -16498,10 +16622,10 @@ async function runEngineScore(opts) {
16498
16622
  for (const g of missingWeights(task.set)) {
16499
16623
  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)`);
16500
16624
  }
16501
- if (opts.rebind !== true && existsSync36(path46.join(candidateDir, "component.json"))) {
16625
+ if (opts.rebind !== true && existsSync37(path47.join(candidateDir, "component.json"))) {
16502
16626
  const prior = (() => {
16503
16627
  try {
16504
- const read = readBundleManifest(readFileSync33(path46.join(candidateDir, "component.json"), "utf8"));
16628
+ const read = readBundleManifest(readFileSync34(path47.join(candidateDir, "component.json"), "utf8"));
16505
16629
  return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
16506
16630
  } catch {
16507
16631
  return { unreadable: true };
@@ -16523,14 +16647,15 @@ async function runEngineScore(opts) {
16523
16647
  }
16524
16648
  }
16525
16649
  const bar = BARS3[opts.bar];
16526
- const evidenceDir = path46.join(candidateDir, "verify-evidence");
16650
+ const evidenceDir = path47.join(candidateDir, "verify-evidence");
16527
16651
  const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
16528
- const parity = await checkHoverParity(task, candidateDir, task.configs);
16652
+ const hoverOpts = opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {};
16653
+ const parity = await checkHoverParity(task, candidateDir, task.configs, { ...hoverOpts, failureShotDir: evidenceDir });
16529
16654
  const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
16530
16655
  const occlusionFailures = occlusionRows.filter((o) => !o.pass);
16531
- const scorePins = composedPins(task.set, [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
16656
+ const scorePins = composedPins(task.set, [opts.library !== void 0 ? path47.resolve(callerCwd, opts.library) : callerCwd]);
16532
16657
  const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
16533
- const behaviors = [...await checkBehaviors(task, candidateDir), ...parity, ...composition];
16658
+ const behaviors = [...await checkBehaviors(task, candidateDir, hoverOpts), ...parity, ...composition];
16534
16659
  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)";
16535
16660
  const obj = objective(scores, behaviors);
16536
16661
  const total = scores.length + behaviors.length;
@@ -16746,11 +16871,11 @@ var codeconnect_exports = {};
16746
16871
  __export(codeconnect_exports, {
16747
16872
  runCodeConnect: () => runCodeConnect
16748
16873
  });
16749
- import { existsSync as existsSync37, readFileSync as readFileSync34, writeFileSync as writeFileSync16 } from "node:fs";
16750
- import path47 from "node:path";
16874
+ import { existsSync as existsSync38, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "node:fs";
16875
+ import path48 from "node:path";
16751
16876
  function runCodeConnect(opts) {
16752
16877
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16753
- const bundleDir = path47.resolve(callerCwd, opts.bundleDir);
16878
+ const bundleDir = path48.resolve(callerCwd, opts.bundleDir);
16754
16879
  let url;
16755
16880
  try {
16756
16881
  url = new URL(opts.figmaUrl);
@@ -16766,7 +16891,7 @@ function runCodeConnect(opts) {
16766
16891
  }
16767
16892
  let manifest;
16768
16893
  try {
16769
- const read = readBundleManifest(readFileSync34(path47.join(bundleDir, "component.json"), "utf8"));
16894
+ const read = readBundleManifest(readFileSync35(path48.join(bundleDir, "component.json"), "utf8"));
16770
16895
  if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
16771
16896
  manifest = read.manifest;
16772
16897
  } catch (err) {
@@ -16776,8 +16901,8 @@ function runCodeConnect(opts) {
16776
16901
  remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
16777
16902
  });
16778
16903
  }
16779
- const setDir = path47.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16780
- if (!existsSync37(path47.join(setDir, "recording-set.json"))) {
16904
+ const setDir = path48.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16905
+ if (!existsSync38(path48.join(setDir, "recording-set.json"))) {
16781
16906
  fail(opts, ExitCode.InputValidation, {
16782
16907
  error: `recording set not found at ${setDir}`,
16783
16908
  code: "codeconnect-no-set",
@@ -16798,10 +16923,10 @@ function runCodeConnect(opts) {
16798
16923
  const component = api.component;
16799
16924
  const recManifest = loadManifest(setDir);
16800
16925
  const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
16801
- const meta = path47.join(setDir, r.slug, "get_metadata.json");
16802
- if (!existsSync37(meta)) return void 0;
16926
+ const meta = path48.join(setDir, r.slug, "get_metadata.json");
16927
+ if (!existsSync38(meta)) return void 0;
16803
16928
  try {
16804
- return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync34(meta, "utf8"))))?.[1];
16929
+ return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync35(meta, "utf8"))))?.[1];
16805
16930
  } catch {
16806
16931
  return void 0;
16807
16932
  }
@@ -16866,7 +16991,7 @@ function runCodeConnect(opts) {
16866
16991
  axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
16867
16992
  fragmentVars.push(varName);
16868
16993
  }
16869
- const entryRel = path47.relative(callerCwd, path47.join(bundleDir, manifest.entry));
16994
+ const entryRel = path48.relative(callerCwd, path48.join(bundleDir, manifest.entry));
16870
16995
  const trust = manifest.trustStatement.split("\n")[0] ?? "";
16871
16996
  const lines = [
16872
16997
  `// url=${opts.figmaUrl}`,
@@ -16890,8 +17015,8 @@ function runCodeConnect(opts) {
16890
17015
  `}`,
16891
17016
  ``
16892
17017
  ].join("\n");
16893
- const outFile = path47.resolve(callerCwd, opts.out ?? path47.join(bundleDir, `${component}.figma.ts`));
16894
- writeFileSync16(outFile, lines);
17018
+ const outFile = path48.resolve(callerCwd, opts.out ?? path48.join(bundleDir, `${component}.figma.ts`));
17019
+ writeFileSync17(outFile, lines);
16895
17020
  emitData(
16896
17021
  opts,
16897
17022
  {
@@ -16930,17 +17055,17 @@ var init_codeconnect = __esm({
16930
17055
 
16931
17056
  // packages/mcp/src/server.ts
16932
17057
  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";
17058
+ import { existsSync as existsSync39, mkdtempSync as mkdtempSync3, readFileSync as readFileSync36, readdirSync as readdirSync16, writeFileSync as writeFileSync18 } from "node:fs";
16934
17059
  import os8 from "node:os";
16935
- import path48 from "node:path";
17060
+ import path49 from "node:path";
16936
17061
  import { fileURLToPath as fileURLToPath6 } from "node:url";
16937
17062
  import { z as z14 } from "zod";
16938
17063
  function sourceHash() {
16939
- const dir = path48.dirname(fileURLToPath6(import.meta.url));
17064
+ const dir = path49.dirname(fileURLToPath6(import.meta.url));
16940
17065
  const h = createHash9("sha256");
16941
- for (const f of readdirSync15(dir).filter((n) => n.endsWith(".ts")).sort()) {
17066
+ for (const f of readdirSync16(dir).filter((n) => n.endsWith(".ts")).sort()) {
16942
17067
  h.update(f);
16943
- h.update(readFileSync35(path48.join(dir, f)));
17068
+ h.update(readFileSync36(path49.join(dir, f)));
16944
17069
  }
16945
17070
  return h.digest("hex").slice(0, 16);
16946
17071
  }
@@ -16948,10 +17073,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
16948
17073
  var init_server = __esm({
16949
17074
  "packages/mcp/src/server.ts"() {
16950
17075
  "use strict";
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] };
17076
+ REPO_ROOT3 = path49.resolve(path49.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
17077
+ CLI_BIN = path49.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
17078
+ BUNDLED_CLI = path49.join(path49.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
17079
+ CLI_SPAWN = existsSync39(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
16955
17080
  str = (d) => z14.string().describe(d);
16956
17081
  optStr = (d) => z14.string().optional().describe(d);
16957
17082
  TOOLS = [
@@ -16982,13 +17107,13 @@ var init_server = __esm({
16982
17107
  const single = i["metadata"];
16983
17108
  const parts = i["metadataParts"];
16984
17109
  if (single !== void 0 || parts !== void 0) {
16985
- const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17110
+ const tmp = path49.join(mkdtempSync3(path49.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
16986
17111
  if (single !== void 0) {
16987
- writeFileSync17(tmp, single);
17112
+ writeFileSync18(tmp, single);
16988
17113
  argvOut.push("--metadata-raw-file", tmp);
16989
17114
  } else {
16990
17115
  if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
16991
- writeFileSync17(tmp, JSON.stringify(parts));
17116
+ writeFileSync18(tmp, JSON.stringify(parts));
16992
17117
  argvOut.push("--metadata-raw-parts-file", tmp);
16993
17118
  }
16994
17119
  }
@@ -17079,14 +17204,14 @@ var init_server = __esm({
17079
17204
  const bridge = (label, single, parts) => {
17080
17205
  if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
17081
17206
  if (single === void 0 && parts === void 0) return;
17082
- const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17207
+ const tmp = path49.join(mkdtempSync3(path49.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17083
17208
  if (single !== void 0) {
17084
- writeFileSync17(tmp, single);
17209
+ writeFileSync18(tmp, single);
17085
17210
  argvOut.push(`--${label}-file`, tmp);
17086
17211
  } else {
17087
17212
  const blocks = parts;
17088
17213
  if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
17089
- writeFileSync17(tmp, JSON.stringify(blocks));
17214
+ writeFileSync18(tmp, JSON.stringify(blocks));
17090
17215
  argvOut.push(`--${label}-parts-file`, tmp);
17091
17216
  }
17092
17217
  };
@@ -17127,12 +17252,12 @@ var init_server = __esm({
17127
17252
  const file = i["file"];
17128
17253
  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)");
17129
17254
  if (file !== void 0) return [...base, "--file", file];
17130
- const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17255
+ const tmp = path49.join(mkdtempSync3(path49.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17131
17256
  if (text !== void 0) {
17132
- writeFileSync17(tmp, text);
17257
+ writeFileSync18(tmp, text);
17133
17258
  return [...base, "--file", tmp, "--raw"];
17134
17259
  }
17135
- writeFileSync17(tmp, JSON.stringify(texts));
17260
+ writeFileSync18(tmp, JSON.stringify(texts));
17136
17261
  return [...base, "--file", tmp, "--raw-parts"];
17137
17262
  }
17138
17263
  },
@@ -17289,13 +17414,13 @@ __export(permissions_exports, {
17289
17414
  runPermissions: () => runPermissions,
17290
17415
  writeSelection: () => writeSelection
17291
17416
  });
17292
- import { existsSync as existsSync39, mkdirSync as mkdirSync11, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
17417
+ import { existsSync as existsSync40, mkdirSync as mkdirSync11, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
17293
17418
  import os9 from "node:os";
17294
- import path49 from "node:path";
17419
+ import path50 from "node:path";
17295
17420
  function mergeAllowlist(file, entries, denyEntries = []) {
17296
17421
  let settings = {};
17297
- if (existsSync39(file) && readFileSync36(file, "utf8").trim() !== "") {
17298
- settings = JSON.parse(readFileSync36(file, "utf8"));
17422
+ if (existsSync40(file) && readFileSync37(file, "utf8").trim() !== "") {
17423
+ settings = JSON.parse(readFileSync37(file, "utf8"));
17299
17424
  if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
17300
17425
  }
17301
17426
  const permissions = settings["permissions"] ??= {};
@@ -17315,8 +17440,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
17315
17440
  }
17316
17441
  if (added.length > 0 || denyAdded.length > 0) {
17317
17442
  allow.push(...added);
17318
- mkdirSync11(path49.dirname(file), { recursive: true });
17319
- writeFileSync18(file, `${JSON.stringify(settings, null, 2)}
17443
+ mkdirSync11(path50.dirname(file), { recursive: true });
17444
+ writeFileSync19(file, `${JSON.stringify(settings, null, 2)}
17320
17445
  `);
17321
17446
  }
17322
17447
  return { added, alreadyPresent, denyAdded };
@@ -17380,7 +17505,7 @@ async function runPermissions(flags) {
17380
17505
  }
17381
17506
  if (flags.write) {
17382
17507
  const base = process.env["INIT_CWD"] ?? process.cwd();
17383
- const file = flags.user ? path49.join(os9.homedir(), ".claude", "settings.json") : path49.join(base, ".claude", "settings.local.json");
17508
+ const file = flags.user ? path50.join(os9.homedir(), ".claude", "settings.json") : path50.join(base, ".claude", "settings.local.json");
17384
17509
  const { entries, denyEntries } = writeSelection(result, flags.user === true);
17385
17510
  if (flags.dryRun) {
17386
17511
  emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
@@ -17529,13 +17654,13 @@ __export(inspect_exports, {
17529
17654
  INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
17530
17655
  runInspect: () => runInspect
17531
17656
  });
17532
- import { existsSync as existsSync40, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
17533
- import path50 from "node:path";
17657
+ import { existsSync as existsSync41, readFileSync as readFileSync38, writeFileSync as writeFileSync20 } from "node:fs";
17658
+ import path51 from "node:path";
17534
17659
  function readVerifyReport(evidenceDir) {
17535
- const p = path50.join(evidenceDir, VERIFY_REPORT_FILENAME);
17536
- if (!existsSync40(p)) return void 0;
17660
+ const p = path51.join(evidenceDir, VERIFY_REPORT_FILENAME);
17661
+ if (!existsSync41(p)) return void 0;
17537
17662
  try {
17538
- return JSON.parse(readFileSync37(p, "utf8"));
17663
+ return JSON.parse(readFileSync38(p, "utf8"));
17539
17664
  } catch {
17540
17665
  return void 0;
17541
17666
  }
@@ -17563,17 +17688,17 @@ async function runInspect(opts) {
17563
17688
  printDescription(INSPECT_DESCRIPTION);
17564
17689
  return;
17565
17690
  }
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)) {
17691
+ const bundleDir = path51.resolve(opts.bundleDir);
17692
+ const evidenceDir = path51.join(bundleDir, "verify-evidence");
17693
+ const manifestPath2 = path51.join(bundleDir, "component.json");
17694
+ if (!existsSync41(evidenceDir) || !existsSync41(manifestPath2)) {
17570
17695
  fail(opts, ExitCode.InputValidation, {
17571
- error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync40(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
17696
+ error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync41(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
17572
17697
  code: "no-evidence",
17573
17698
  remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
17574
17699
  });
17575
17700
  }
17576
- const { manifest } = readBundleManifest(readFileSync37(manifestPath2, "utf8"));
17701
+ const { manifest } = readBundleManifest(readFileSync38(manifestPath2, "utf8"));
17577
17702
  if (manifest === void 0) {
17578
17703
  fail(opts, ExitCode.InputValidation, {
17579
17704
  error: "component.json did not parse as a bundle manifest",
@@ -17581,9 +17706,9 @@ async function runInspect(opts) {
17581
17706
  remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
17582
17707
  });
17583
17708
  }
17584
- const setDir = path50.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17709
+ const setDir = path51.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17585
17710
  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`)));
17711
+ const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync41(path51.join(evidenceDir, `${rep}-ref.png`)) && existsSync41(path51.join(evidenceDir, `${rep}-render.png`)));
17587
17712
  if (reps.length === 0) {
17588
17713
  fail(opts, ExitCode.InputValidation, {
17589
17714
  error: "verify-evidence holds no ref/render pairs for this bundle's configs",
@@ -17594,15 +17719,15 @@ async function runInspect(opts) {
17594
17719
  let crops = 0;
17595
17720
  const sections = [];
17596
17721
  for (const rep of reps) {
17597
- const ref = new Uint8Array(readFileSync37(path50.join(evidenceDir, `${rep}-ref.png`)));
17598
- const render = new Uint8Array(readFileSync37(path50.join(evidenceDir, `${rep}-render.png`)));
17722
+ const ref = new Uint8Array(readFileSync38(path51.join(evidenceDir, `${rep}-ref.png`)));
17723
+ const render = new Uint8Array(readFileSync38(path51.join(evidenceDir, `${rep}-render.png`)));
17599
17724
  const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
17600
17725
  const cells = [];
17601
17726
  for (const [i, n] of nodes.entries()) {
17602
17727
  const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
17603
17728
  try {
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));
17729
+ writeFileSync20(path51.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
17730
+ writeFileSync20(path51.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
17606
17731
  } catch {
17607
17732
  continue;
17608
17733
  }
@@ -17619,8 +17744,8 @@ async function runInspect(opts) {
17619
17744
  if (reps.includes(c.rep)) continue;
17620
17745
  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
17746
  }
17622
- const sheet = path50.join(evidenceDir, "inspect.html");
17623
- writeFileSync19(
17747
+ const sheet = path51.join(evidenceDir, "inspect.html");
17748
+ writeFileSync20(
17624
17749
  sheet,
17625
17750
  `<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
17626
17751
  body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
@@ -17693,8 +17818,8 @@ __export(login_exports, {
17693
17818
  runLogout: () => runLogout
17694
17819
  });
17695
17820
  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";
17821
+ import { existsSync as existsSync42, mkdirSync as mkdirSync12, readFileSync as readFileSync39, rmSync as rmSync6, writeFileSync as writeFileSync21 } from "node:fs";
17822
+ import path52 from "node:path";
17698
17823
  import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
17699
17824
  async function runLogin(opts, deps) {
17700
17825
  const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
@@ -17788,13 +17913,13 @@ function settleDecision(opts, origin, outcome) {
17788
17913
  }
17789
17914
  }
17790
17915
  function pendingLoginPath() {
17791
- return path51.join(path51.dirname(sessionPath()), "pending-login.json");
17916
+ return path52.join(path52.dirname(sessionPath()), "pending-login.json");
17792
17917
  }
17793
17918
  async function deviceStartPhase(opts, origin, deps) {
17794
17919
  const started = await startHandshake(opts, origin, deps);
17795
17920
  const file = pendingLoginPath();
17796
- mkdirSync12(path51.dirname(file), { recursive: true });
17797
- writeFileSync20(file, `${JSON.stringify({ origin, ...started }, null, 2)}
17921
+ mkdirSync12(path52.dirname(file), { recursive: true });
17922
+ writeFileSync21(file, `${JSON.stringify({ origin, ...started }, null, 2)}
17798
17923
  `, { mode: 384 });
17799
17924
  deps.openBrowser(started.verificationUrl);
17800
17925
  emitData(
@@ -17819,9 +17944,9 @@ async function deviceStartPhase(opts, origin, deps) {
17819
17944
  async function deviceWaitPhase(opts, deps) {
17820
17945
  const file = pendingLoginPath();
17821
17946
  let pending;
17822
- if (existsSync41(file)) {
17947
+ if (existsSync42(file)) {
17823
17948
  try {
17824
- const parsed = JSON.parse(readFileSync38(file, "utf8"));
17949
+ const parsed = JSON.parse(readFileSync39(file, "utf8"));
17825
17950
  if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
17826
17951
  pending = parsed;
17827
17952
  }
@@ -18072,10 +18197,10 @@ var publish_exports = {};
18072
18197
  __export(publish_exports, {
18073
18198
  runPublish: () => runPublish
18074
18199
  });
18075
- import { existsSync as existsSync42, readFileSync as readFileSync39 } from "node:fs";
18076
- import path52 from "node:path";
18200
+ import { existsSync as existsSync43, readFileSync as readFileSync40 } from "node:fs";
18201
+ import path53 from "node:path";
18077
18202
  async function runPublish(opts) {
18078
- const bundleDir = path52.resolve(opts.bundleDir);
18203
+ const bundleDir = path53.resolve(opts.bundleDir);
18079
18204
  const bundle = readBundle(opts, bundleDir);
18080
18205
  const report = bundle.report;
18081
18206
  if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
@@ -18120,7 +18245,7 @@ async function runPublish(opts) {
18120
18245
  const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
18121
18246
  if (sheetEntry !== void 0) {
18122
18247
  const missingCrops = missingInspectCrops(
18123
- readFileSync39(path52.join(bundleDir, sheetEntry.path), "utf8"),
18248
+ readFileSync40(path53.join(bundleDir, sheetEntry.path), "utf8"),
18124
18249
  surface.published.map((p) => p.path)
18125
18250
  );
18126
18251
  if (missingCrops.length > 0) {
@@ -18225,8 +18350,8 @@ async function runPublish(opts) {
18225
18350
  }
18226
18351
  const uploaded = [];
18227
18352
  for (const object of opened.value.plan.objects) {
18228
- const file = path52.join(bundleDir, object.relPath);
18229
- if (!existsSync42(file)) {
18353
+ const file = path53.join(bundleDir, object.relPath);
18354
+ if (!existsSync43(file)) {
18230
18355
  fail(opts, ExitCode.InputValidation, {
18231
18356
  error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
18232
18357
  code: "planned-file-missing",
@@ -18236,7 +18361,7 @@ async function runPublish(opts) {
18236
18361
  const sent = await client.upload({
18237
18362
  publicationId: opened.value.publicationId,
18238
18363
  relPath: object.relPath,
18239
- bytes: new Uint8Array(readFileSync39(file))
18364
+ bytes: new Uint8Array(readFileSync40(file))
18240
18365
  });
18241
18366
  if (!sent.ok) refuse2(opts, sent, "upload-refused");
18242
18367
  uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
@@ -18278,23 +18403,23 @@ async function runPublish(opts) {
18278
18403
  );
18279
18404
  }
18280
18405
  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)) {
18406
+ const manifestPath2 = path53.join(bundleDir, "component.json");
18407
+ const reportPath = path53.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
18408
+ if (!existsSync43(manifestPath2)) {
18284
18409
  fail(opts, ExitCode.InputValidation, {
18285
18410
  error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
18286
18411
  code: "not-a-bundle",
18287
18412
  remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
18288
18413
  });
18289
18414
  }
18290
- if (!existsSync42(reportPath)) {
18415
+ if (!existsSync43(reportPath)) {
18291
18416
  fail(opts, ExitCode.InputValidation, {
18292
18417
  error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
18293
18418
  code: "bundle-not-verified",
18294
18419
  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
18420
  });
18296
18421
  }
18297
- const { manifest } = readBundleManifest(readFileSync39(manifestPath2, "utf8"));
18422
+ const { manifest } = readBundleManifest(readFileSync40(manifestPath2, "utf8"));
18298
18423
  if (manifest === void 0) {
18299
18424
  fail(opts, ExitCode.InputValidation, {
18300
18425
  error: "component.json did not parse as a bundle manifest",
@@ -18302,7 +18427,7 @@ function readBundle(opts, bundleDir) {
18302
18427
  remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
18303
18428
  });
18304
18429
  }
18305
- const reportText = readFileSync39(reportPath, "utf8");
18430
+ const reportText = readFileSync40(reportPath, "utf8");
18306
18431
  let report;
18307
18432
  try {
18308
18433
  report = JSON.parse(reportText);
@@ -18397,17 +18522,17 @@ __export(generate_recorded_exports, {
18397
18522
  runGenerateRecorded: () => runGenerateRecorded
18398
18523
  });
18399
18524
  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";
18525
+ import { existsSync as existsSync44, readFileSync as readFileSync41 } from "node:fs";
18526
+ import path54 from "node:path";
18402
18527
  async function runGenerateRecorded(opts) {
18403
18528
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
18404
- const outDirAbs = path53.resolve(callerCwd, opts.out);
18405
- const recordedAsPath = path53.resolve(callerCwd, opts.recorded);
18529
+ const outDirAbs = path54.resolve(callerCwd, opts.out);
18530
+ const recordedAsPath = path54.resolve(callerCwd, opts.recorded);
18406
18531
  let task;
18407
18532
  let taskName;
18408
18533
  let authoredApi;
18409
18534
  let composition;
18410
- const isSet = existsSync43(path53.join(recordedAsPath, "recording-set.json"));
18535
+ const isSet = existsSync44(path54.join(recordedAsPath, "recording-set.json"));
18411
18536
  const registry = TASKS[opts.recorded];
18412
18537
  if (registry !== void 0 && !isSet) {
18413
18538
  task = registry;
@@ -18416,7 +18541,7 @@ async function runGenerateRecorded(opts) {
18416
18541
  try {
18417
18542
  const authored = authorTaskFromSet(recordedAsPath);
18418
18543
  task = authored.task;
18419
- taskName = path53.basename(recordedAsPath);
18544
+ taskName = path54.basename(recordedAsPath);
18420
18545
  authoredApi = authored.api;
18421
18546
  const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
18422
18547
  if (roles.success) composition = roles.data;
@@ -18450,7 +18575,7 @@ async function runGenerateRecorded(opts) {
18450
18575
  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)`);
18451
18576
  }
18452
18577
  const missing = task.configs.filter(
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"))
18578
+ (c) => !existsSync44(path54.join(task.set, c.rep, "get_screenshot.json")) || !existsSync44(path54.join(task.set, c.rep, "get_metadata.json")) || !existsSync44(path54.join(task.set, c.rep, "get_design_context.json"))
18454
18579
  );
18455
18580
  if (missing.length > 0) {
18456
18581
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -18520,8 +18645,8 @@ async function runGenerateRecorded(opts) {
18520
18645
  ` : `${line}
18521
18646
  `);
18522
18647
  if (opts.dryRun) {
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)})
18648
+ emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path54.join(outDirAbs, taskName) }, () => {
18649
+ process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path54.join(outDirAbs, taskName)})
18525
18650
  `);
18526
18651
  });
18527
18652
  return;
@@ -18544,10 +18669,10 @@ async function runGenerateRecorded(opts) {
18544
18669
  });
18545
18670
  }
18546
18671
  }
18547
- const bundleDir = path53.join(outDirAbs, taskName);
18548
- if (existsSync43(path53.join(bundleDir, "component.json"))) {
18672
+ const bundleDir = path54.join(outDirAbs, taskName);
18673
+ if (existsSync44(path54.join(bundleDir, "component.json"))) {
18549
18674
  try {
18550
- const prior = readBundleManifest(readFileSync40(path53.join(bundleDir, "component.json"), "utf8")).manifest;
18675
+ const prior = readBundleManifest(readFileSync41(path54.join(bundleDir, "component.json"), "utf8")).manifest;
18551
18676
  if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
18552
18677
  fail(opts, ExitCode.InputValidation, {
18553
18678
  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`,
@@ -18714,7 +18839,7 @@ init_invocation();
18714
18839
  init_output();
18715
18840
  import { intro, isCancel, outro, password } from "@clack/prompts";
18716
18841
  import fs from "node:fs";
18717
- import path29 from "node:path";
18842
+ import path30 from "node:path";
18718
18843
  var INIT_DESCRIPTION = {
18719
18844
  name: "init",
18720
18845
  summary: "Configure the OpenRouter credential in .env, and optionally a Figma token (idempotent).",
@@ -18756,7 +18881,7 @@ async function runInit(flags) {
18756
18881
  printDescription(INIT_DESCRIPTION);
18757
18882
  return;
18758
18883
  }
18759
- const envPath = path29.resolve(process.cwd(), ".env");
18884
+ const envPath = path30.resolve(process.cwd(), ".env");
18760
18885
  const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
18761
18886
  let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
18762
18887
  let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
@@ -18777,7 +18902,7 @@ async function runInit(flags) {
18777
18902
  if (openrouterKey) next.set(ENV_KEYS.openrouter, openrouterKey);
18778
18903
  if (figmaToken) next.set(ENV_KEYS.figma, figmaToken);
18779
18904
  const changed = [...next].some(([key, value]) => existing.get(key) !== value);
18780
- const gitignorePath = path29.resolve(process.cwd(), ".gitignore");
18905
+ const gitignorePath = path30.resolve(process.cwd(), ".gitignore");
18781
18906
  const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
18782
18907
  const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
18783
18908
  if (flags.dryRun) {
@@ -18833,14 +18958,14 @@ init_invocation();
18833
18958
  init_output();
18834
18959
  init_entitlement();
18835
18960
  import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
18836
- import { readFileSync as readFileSync21, readdirSync as readdirSync9, existsSync as existsSync23 } from "node:fs";
18961
+ import { readFileSync as readFileSync22, readdirSync as readdirSync9, existsSync as existsSync24 } from "node:fs";
18837
18962
 
18838
18963
  // packages/cli/src/pipeline.ts
18839
18964
  init_src2();
18840
18965
  init_src5();
18841
18966
  init_src4();
18842
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "node:fs";
18843
- import path30 from "node:path";
18967
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "node:fs";
18968
+ import path31 from "node:path";
18844
18969
 
18845
18970
  // packages/cli/src/assets-module.ts
18846
18971
  init_src();
@@ -19176,7 +19301,7 @@ async function runGenerationPipeline(input) {
19176
19301
  });
19177
19302
  const written = [];
19178
19303
  if (!input.dryRun) {
19179
- const dir = path30.resolve(input.outDir, semantics.componentName);
19304
+ const dir = path31.resolve(input.outDir, semantics.componentName);
19180
19305
  mkdirSync6(dir, { recursive: true });
19181
19306
  const files = {
19182
19307
  // Bundle-local tokens: THE emission the component's CSS resolves
@@ -19200,14 +19325,14 @@ async function runGenerationPipeline(input) {
19200
19325
  `
19201
19326
  };
19202
19327
  for (const [name, content] of Object.entries(files)) {
19203
- const filePath = path30.join(dir, name);
19204
- writeFileSync9(filePath, content);
19328
+ const filePath = path31.join(dir, name);
19329
+ writeFileSync10(filePath, content);
19205
19330
  written.push(filePath);
19206
19331
  }
19207
19332
  for (const artifact of emitTokenArtifacts(input.mapping)) {
19208
- const filePath = path30.resolve(input.outDir, artifact.path);
19209
- mkdirSync6(path30.dirname(filePath), { recursive: true });
19210
- writeFileSync9(filePath, artifact.content);
19333
+ const filePath = path31.resolve(input.outDir, artifact.path);
19334
+ mkdirSync6(path31.dirname(filePath), { recursive: true });
19335
+ writeFileSync10(filePath, artifact.content);
19211
19336
  written.push(filePath);
19212
19337
  }
19213
19338
  }
@@ -19265,7 +19390,7 @@ var GENERATE_DESCRIPTION = {
19265
19390
  function resolveProvidedSource(flags, contextFile) {
19266
19391
  let raw;
19267
19392
  try {
19268
- raw = readFileSync21(contextFile, "utf8");
19393
+ raw = readFileSync22(contextFile, "utf8");
19269
19394
  } catch {
19270
19395
  fail(flags, ExitCode.InputValidation, {
19271
19396
  error: `Cannot read context file "${contextFile}".`,
@@ -19385,11 +19510,11 @@ token mapping (${mapping.flat.length} variables):
19385
19510
  let initialCode;
19386
19511
  let initialSemantics;
19387
19512
  try {
19388
- if (existsSync23(flags.out)) {
19513
+ if (existsSync24(flags.out)) {
19389
19514
  for (const entry of readdirSync9(flags.out)) {
19390
19515
  const cjPath = `${flags.out}/${entry}/component.json`;
19391
- if (!existsSync23(cjPath)) continue;
19392
- const cj = JSON.parse(readFileSync21(cjPath, "utf8"));
19516
+ if (!existsSync24(cjPath)) continue;
19517
+ const cj = JSON.parse(readFileSync22(cjPath, "utf8"));
19393
19518
  if (cj.name !== void 0 && Array.isArray(cj.props)) {
19394
19519
  previousApi = JSON.stringify({
19395
19520
  componentName: cj.name,
@@ -19397,14 +19522,14 @@ token mapping (${mapping.flat.length} variables):
19397
19522
  });
19398
19523
  const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
19399
19524
  const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
19400
- if (flags.refine && existsSync23(tsxPath) && existsSync23(cssPath)) {
19525
+ if (flags.refine && existsSync24(tsxPath) && existsSync24(cssPath)) {
19401
19526
  initialCode = {
19402
- tsx: readFileSync21(tsxPath, "utf8"),
19403
- css: readFileSync21(cssPath, "utf8")
19527
+ tsx: readFileSync22(tsxPath, "utf8"),
19528
+ css: readFileSync22(cssPath, "utf8")
19404
19529
  };
19405
19530
  const semPath = `${flags.out}/${entry}/semantics.json`;
19406
- if (existsSync23(semPath)) {
19407
- initialSemantics = JSON.parse(readFileSync21(semPath, "utf8"));
19531
+ if (existsSync24(semPath)) {
19532
+ initialSemantics = JSON.parse(readFileSync22(semPath, "utf8"));
19408
19533
  }
19409
19534
  }
19410
19535
  break;
@@ -19544,6 +19669,15 @@ bundle written to ${flags.out}/${result.semantics.componentName}/:
19544
19669
 
19545
19670
  // packages/cli/src/program.ts
19546
19671
  init_src5();
19672
+ function parseHoverTimeout(raw) {
19673
+ const ms = Number(raw);
19674
+ if (!Number.isInteger(ms) || ms < 100 || ms > 6e4) {
19675
+ process.stderr.write(`--hover-timeout must be an integer between 100 and 60000 milliseconds, got "${raw}"
19676
+ `);
19677
+ process.exit(2);
19678
+ }
19679
+ return ms;
19680
+ }
19547
19681
  function globalFlags(cmd) {
19548
19682
  const opts = cmd.optsWithGlobals();
19549
19683
  return {
@@ -19735,7 +19869,7 @@ function buildProgram() {
19735
19869
  ...local["profile"] !== void 0 ? { profile: local["profile"] } : {}
19736
19870
  });
19737
19871
  });
19738
- engine.command("score").argument("<taskOrSet>", "reference task name or recording-set directory").argument("<candidateDir>", "directory containing the proposed bundle files").option("--bar <bar>", "target bar: pass (0.95) or cert (0.97)", "pass").option("--host <name>", "self-reported host identity (recorded in provenance, labeled self-reported)").requiredOption("--model <id>", "proposer model, self-reported \u2014 required; a score without a declared model is not accepted").option("--rebind", "explicitly re-bind an already-bound bundle to a different recording set (refused otherwise \u2014 rebinding rewrites the bundle's verification identity)").option("--library <dir>", "workspace root holding confirmed partners generated bundles (ADR-013 composed pins; default: current directory)").action(async (taskOrSet, candidateDir, _o, cmd) => {
19872
+ engine.command("score").argument("<taskOrSet>", "reference task name or recording-set directory").argument("<candidateDir>", "directory containing the proposed bundle files").option("--bar <bar>", "target bar: pass (0.95) or cert (0.97)", "pass").option("--host <name>", "self-reported host identity (recorded in provenance, labeled self-reported)").requiredOption("--model <id>", "proposer model, self-reported \u2014 required; a score without a declared model is not accepted").option("--rebind", "explicitly re-bind an already-bound bundle to a different recording set (refused otherwise \u2014 rebinding rewrites the bundle's verification identity)").option("--library <dir>", "workspace root holding confirmed partners generated bundles (ADR-013 composed pins; default: current directory)").option("--hover-timeout <ms>", "hover actionability budget in ms (default 2000) \u2014 loop feedback only here; a certifying verify run records any non-default budget as a caveat").action(async (taskOrSet, candidateDir, _o, cmd) => {
19739
19873
  const flags = globalFlags(cmd.parent.parent);
19740
19874
  const local = cmd.opts();
19741
19875
  const { runEngineScore: runEngineScore2 } = await Promise.resolve().then(() => (init_engine2(), engine_exports));
@@ -19747,7 +19881,8 @@ function buildProgram() {
19747
19881
  ...local["host"] !== void 0 ? { host: local["host"] } : {},
19748
19882
  model: local["model"],
19749
19883
  rebind: local["rebind"],
19750
- ...local["library"] !== void 0 ? { library: local["library"] } : {}
19884
+ ...local["library"] !== void 0 ? { library: local["library"] } : {},
19885
+ ...local["hoverTimeout"] !== void 0 ? { hoverTimeoutMs: parseHoverTimeout(local["hoverTimeout"]) } : {}
19751
19886
  });
19752
19887
  });
19753
19888
  program.command("codeconnect").description("Emit a Figma Code Connect template (.figma.ts) for a certified bundle \u2014 every variant\u2192prop mapping from recorded truth, stamped with the trust statement. Publishing stays yours (figma connect publish; Org/Enterprise plan).").argument("<bundleDir>", "bundle directory (must carry component.json)").requiredOption("--figma-url <url>", "figma.com /design/ URL of the COMPONENT SET (Copy link to selection)").option("--set <dir>", "recording set override (default: the bundle's provenance path)").option("--out <file>", "output file (default: <bundle>/<Component>.figma.ts)").action(async (bundleDir, _o, cmd) => {
@@ -19843,7 +19978,7 @@ function buildProgram() {
19843
19978
  ...local["confirmPublish"] !== void 0 ? { confirmPublish: local["confirmPublish"] } : {}
19844
19979
  });
19845
19980
  });
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) => {
19981
+ 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)").option("--hover-timeout <ms>", "hover actionability budget in ms (default 2000) \u2014 for diagnosing a slow machine; a non-default value is recorded in the report as a verdict caveat, never silently").action(async (bundleDir, _opts, cmd) => {
19847
19982
  const flags = globalFlags(cmd);
19848
19983
  const local = cmd.opts();
19849
19984
  const bar = local["bar"] === "cert" ? "cert" : "pass";
@@ -19855,7 +19990,8 @@ function buildProgram() {
19855
19990
  ...local["set"] !== void 0 ? { set: local["set"] } : {},
19856
19991
  ...local["library"] !== void 0 ? { library: local["library"] } : {},
19857
19992
  bar,
19858
- ...local["profile"] !== void 0 ? { profile: local["profile"] } : {}
19993
+ ...local["profile"] !== void 0 ? { profile: local["profile"] } : {},
19994
+ ...local["hoverTimeout"] !== void 0 ? { hoverTimeoutMs: parseHoverTimeout(local["hoverTimeout"]) } : {}
19859
19995
  });
19860
19996
  });
19861
19997
  program.command("generate").description(