@tendrilapp/cli 0.1.41 → 0.1.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tendril.js CHANGED
@@ -1980,8 +1980,8 @@ var init_src = __esm({
1980
1980
  function variableNameToPath(name) {
1981
1981
  return name.split("/").map(canonicalCssIdentPart).filter((part) => part.length > 0);
1982
1982
  }
1983
- function tokenPathToCssVar(path54) {
1984
- return `--${path54.join("-")}`;
1983
+ function tokenPathToCssVar(path56) {
1984
+ return `--${path56.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 path56 = variableNameToPath(variable.name);
2029
+ if (path56.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: path56 };
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: path56 } 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 path56.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 = path56[path56.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 "${path56.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: path56.join("."),
2067
+ cssVar: tokenPathToCssVar(path56),
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 path56 = ctx.pathById.get(id);
2257
+ if (path56 === void 0) ctx.unresolved.add(id);
2258
+ return path56;
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 path56 = resolveBinding(ctx, id);
2294
+ if (path56 !== void 0) tokens.add(path56);
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 path56 = resolveBinding(ctx, radiusId);
2303
+ if (path56 !== void 0) tokens.add(path56);
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 path56 = resolveBinding(ctx, gapId);
2314
+ if (path56 !== void 0) {
2315
+ layout.gap = path56;
2316
+ tokens.add(path56);
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 path56 = resolveBinding(ctx, id);
2326
+ if (path56 !== void 0) {
2327
+ paddingPaths.push(path56);
2328
+ tokens.add(path56);
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,43 @@ 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 path56 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
6243
+ const inEvidence = path56.startsWith(`${EVIDENCE_DIR}/`);
6244
+ if (path56.startsWith("fonts/")) {
6245
+ const fname = path56.slice("fonts/".length);
6246
+ if (!fname.includes("/") && (/\.(woff2?|ttf|otf)$/i.test(fname) || /^(NOTICE|LICENSE|LICENCE)[^/]*\.txt$/i.test(fname))) {
6247
+ excluded.push({ path: path56, reason: "font payload \u2014 not published (fonts policy pending); faces are sha-pinned in component.json requiredFonts" });
6248
+ continue;
6249
+ }
6250
+ unknown.push(path56);
6251
+ continue;
6252
+ }
6253
+ const name = inEvidence ? path56.slice(EVIDENCE_DIR.length + 1) : path56;
6184
6254
  if (name.includes("/")) {
6185
- unknown.push(path54);
6255
+ unknown.push(path56);
6186
6256
  continue;
6187
6257
  }
6188
6258
  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" });
6259
+ if (name === "verify-report.json") published.push({ path: path56, role: "verify-report" });
6260
+ else if (name === "diff-legend.txt") published.push({ path: path56, role: "diff-legend" });
6261
+ else if (name === "inspect.html") published.push({ path: path56, role: "inspect-sheet" });
6262
+ else if (/-mount-failure\.png$/.test(name)) excluded.push({ path: path56, reason: "harness failure diagnostic (regenerated every verify run, never published)" });
6192
6263
  else {
6193
6264
  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);
6265
+ if (hit !== void 0) published.push({ path: path56, role: hit.role });
6266
+ else unknown.push(path56);
6196
6267
  }
6197
6268
  continue;
6198
6269
  }
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" });
6270
+ if (name === opts.entry) published.push({ path: path56, role: "entry" });
6271
+ else if (name === "styles.css") published.push({ path: path56, role: "styles" });
6272
+ else if (name === "tokens.css") published.push({ path: path56, role: "tokens" });
6273
+ else if (name === "fonts.css") published.push({ path: path56, role: "fonts" });
6274
+ else if (name === "component.json") published.push({ path: path56, role: "manifest" });
6204
6275
  else {
6205
6276
  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);
6277
+ if (skip !== void 0) excluded.push({ path: path56, reason: skip.reason });
6278
+ else unknown.push(path56);
6208
6279
  }
6209
6280
  }
6210
6281
  const roles = new Set(published.map((p) => p.role));
@@ -6214,8 +6285,8 @@ function missingInspectCrops(sheetText, publishedPaths) {
6214
6285
  const held = new Set(publishedPaths);
6215
6286
  const missing = /* @__PURE__ */ new Set();
6216
6287
  for (const [, name] of sheetText.matchAll(/src="\.\/([^"]+)"/g)) {
6217
- const path54 = `${EVIDENCE_DIR}/${name}`;
6218
- if (!held.has(path54)) missing.add(path54);
6288
+ const path56 = `${EVIDENCE_DIR}/${name}`;
6289
+ if (!held.has(path56)) missing.add(path56);
6219
6290
  }
6220
6291
  return [...missing].sort();
6221
6292
  }
@@ -6323,10 +6394,10 @@ function readScoredFiles(report) {
6323
6394
  const entries = Object.entries(value);
6324
6395
  if (entries.length === 0) return void 0;
6325
6396
  const out = {};
6326
- for (const [path54, digest] of entries) {
6327
- if (path54 === "" || path54.startsWith("/") || path54.includes("..")) return void 0;
6397
+ for (const [path56, digest] of entries) {
6398
+ if (path56 === "" || path56.startsWith("/") || path56.includes("..")) return void 0;
6328
6399
  if (!isSetHash(digest)) return void 0;
6329
- out[path54] = digest;
6400
+ out[path56] = digest;
6330
6401
  }
6331
6402
  return out;
6332
6403
  }
@@ -6334,11 +6405,11 @@ function compareScoredFiles(recorded, actual) {
6334
6405
  const missing = [];
6335
6406
  const unscored = [];
6336
6407
  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);
6408
+ for (const [path56, digest] of Object.entries(recorded)) {
6409
+ if (!(path56 in actual)) missing.push(path56);
6410
+ else if (actual[path56] !== digest) changed.push(path56);
6340
6411
  }
6341
- for (const path54 of Object.keys(actual)) if (!(path54 in recorded)) unscored.push(path54);
6412
+ for (const path56 of Object.keys(actual)) if (!(path56 in recorded)) unscored.push(path56);
6342
6413
  return { missing: missing.sort(), unscored: unscored.sort(), changed: changed.sort() };
6343
6414
  }
6344
6415
  function scoredRecordingSetHash(report) {
@@ -7317,7 +7388,7 @@ body{margin:0;background:rgb(${backdrop.join(",")})}
7317
7388
  inkRecall: 0,
7318
7389
  exact: { similarity: 0, inkRecall: 0 },
7319
7390
  pass: false,
7320
- error: `mount failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`
7391
+ error: `mount failed: ${describeMountError(err)}`
7321
7392
  })
7322
7393
  );
7323
7394
  const deadlineMs = timeoutMs + 1e4;
@@ -7361,6 +7432,7 @@ var init_bundle_score = __esm({
7361
7432
  init_effect_geometry();
7362
7433
  init_image_diff();
7363
7434
  init_font_faces();
7435
+ init_mount_error();
7364
7436
  init_mount_limits();
7365
7437
  init_tasks();
7366
7438
  BAR = { sim: 0.95, ink: 0.95 };
@@ -7401,6 +7473,8 @@ var init_prelude = __esm({
7401
7473
  });
7402
7474
 
7403
7475
  // packages/verify/src/parity.ts
7476
+ import { writeFileSync as writeFileSync7 } from "node:fs";
7477
+ import path22 from "node:path";
7404
7478
  import { chromium as chromium6 } from "playwright-core";
7405
7479
  function getFontFaces3() {
7406
7480
  _fontFaces3 ??= fontFaceCss();
@@ -7445,6 +7519,8 @@ async function checkHoverParity(task, bundleDir, authority, opts = {}) {
7445
7519
  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
7520
  #root > *{min-width:${box.w}px;min-height:${box.h}px}`;
7447
7521
  const viewport = box === void 0 ? { width: 900, height: 700 } : { width: box.w + 48, height: box.h + 48 };
7522
+ let consoleTail = "";
7523
+ let failureShot;
7448
7524
  const shoot = async (component, props, realHover) => {
7449
7525
  const html = `<!doctype html><html><head><meta charset="utf-8"><style>
7450
7526
  ${getFontFaces3()}
@@ -7456,13 +7532,23 @@ ${rootCss}
7456
7532
  const page = await browser.newPage({ viewport });
7457
7533
  try {
7458
7534
  page.setDefaultTimeout(timeoutMs);
7535
+ const consoleOf = capturePageConsole(page);
7459
7536
  await page.route("**/*", (route) => route.request().url().startsWith("data:") ? route.continue() : route.abort());
7460
7537
  await page.setContent(html, { waitUntil: "load" });
7461
7538
  await page.waitForTimeout(250);
7462
7539
  const rendered = await page.evaluate("document.querySelector('#root > *') !== null");
7463
7540
  if (rendered !== true) return { error: `${cfg.component} rendered nothing` };
7464
7541
  if (realHover) {
7465
- await page.hover("#root > *", { timeout: 2e3 });
7542
+ try {
7543
+ await hoverWithBudget(page, "#root > *", opts.hoverBudgetMs ?? DEFAULT_HOVER_BUDGET_MS);
7544
+ } catch (err) {
7545
+ consoleTail = consoleOf();
7546
+ try {
7547
+ failureShot = await page.screenshot();
7548
+ } catch {
7549
+ }
7550
+ throw err;
7551
+ }
7466
7552
  await page.waitForTimeout(400);
7467
7553
  } else {
7468
7554
  await page.waitForTimeout(400);
@@ -7501,7 +7587,19 @@ ${rootCss}
7501
7587
  };
7502
7588
  }
7503
7589
  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)}` }));
7590
+ })().catch((err) => {
7591
+ let detail = `mount failed: ${describeMountError(err)}`;
7592
+ if (consoleTail !== "") detail += ` | browser console: ${consoleTail}`;
7593
+ if (failureShot !== void 0 && opts.failureShotDir !== void 0) {
7594
+ const name = `parity-${cfg.rep}-mount-failure.png`;
7595
+ try {
7596
+ writeFileSync7(path22.join(opts.failureShotDir, name), failureShot);
7597
+ detail += ` | failure screenshot: ${name}`;
7598
+ } catch {
7599
+ }
7600
+ }
7601
+ return { id: `parity:${cfg.rep}`, pass: false, detail };
7602
+ });
7505
7603
  const result = await raceMountDeadline(
7506
7604
  work,
7507
7605
  deadlineMs,
@@ -7531,6 +7629,7 @@ var init_parity = __esm({
7531
7629
  init_candidate_css();
7532
7630
  init_behavior();
7533
7631
  init_font_faces();
7632
+ init_mount_error();
7534
7633
  init_mount_limits();
7535
7634
  init_bundle_score();
7536
7635
  }
@@ -7538,8 +7637,8 @@ var init_parity = __esm({
7538
7637
 
7539
7638
  // packages/verify/src/composition.ts
7540
7639
  import { createRequire as createRequire2 } from "node:module";
7541
- import { existsSync as existsSync16 } from "node:fs";
7542
- import path22 from "node:path";
7640
+ import { existsSync as existsSync17 } from "node:fs";
7641
+ import path23 from "node:path";
7543
7642
  import { build as build6 } from "esbuild";
7544
7643
  import { chromium as chromium7 } from "playwright-core";
7545
7644
  function getFontFaces4() {
@@ -7547,21 +7646,21 @@ function getFontFaces4() {
7547
7646
  return _fontFaces4;
7548
7647
  }
7549
7648
  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"));
7649
+ const entryTsx = path23.join(bundleDir, task.entry);
7650
+ if (!existsSync17(entryTsx)) return { error: `${task.entry} missing` };
7651
+ const requireFromVerify = createRequire2(path23.join(VERIFY_PKG_DIR, "package.json"));
7553
7652
  let realJsxPath;
7554
7653
  try {
7555
7654
  realJsxPath = requireFromVerify.resolve("react/jsx-runtime");
7556
7655
  } catch (err) {
7557
- return { error: `cannot resolve react/jsx-runtime: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` };
7656
+ return { error: `cannot resolve react/jsx-runtime: ${describeMountError(err)}` };
7558
7657
  }
7559
7658
  const mountSrc = `
7560
7659
  import { createElement } from "react";
7561
7660
  import { createRoot } from "react-dom/client";
7562
7661
  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")}
7662
+ import * as B from ${JSON.stringify(path23.resolve(entryTsx))};
7663
+ ${composedParts.map((p, i) => `import * as CP${i} from ${JSON.stringify(path23.resolve(bundleDir, p.modulePath))};`).join("\n")}
7565
7664
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; partComponents: string[] } }).__cfg;
7566
7665
  const pairs: Array<[unknown, string]> = [];
7567
7666
  for (const name of cfg.partComponents) {
@@ -7600,7 +7699,7 @@ if (root && Main) createRoot(root).render(createElement(Main, cfg.props));
7600
7699
  });
7601
7700
  return bundle.outputFiles[0]?.text ?? "";
7602
7701
  } catch (err) {
7603
- return { error: `does not compile: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` };
7702
+ return { error: `does not compile: ${describeMountError(err)}` };
7604
7703
  }
7605
7704
  }
7606
7705
  function expectedParts(task, roles, mainSlug) {
@@ -7617,7 +7716,7 @@ function expectedParts(task, roles, mainSlug) {
7617
7716
  }
7618
7717
  function interiorRegions(setDir, roles) {
7619
7718
  const mains = roles.main;
7620
- const withInterior = mains.filter((m) => existsSync16(path22.join(setDir, m, "get_metadata_interior.json")));
7719
+ const withInterior = mains.filter((m) => existsSync17(path23.join(setDir, m, "get_metadata_interior.json")));
7621
7720
  if (withInterior.length === 0) {
7622
7721
  return { unavailable: "no interior geometry recorded for any main (pre-obligation set) \u2014 re-record with `tendril record` to enable crop checks" };
7623
7722
  }
@@ -7681,7 +7780,7 @@ async function checkCropComposition(task, bundleDir, regions, opts = {}) {
7681
7780
  }
7682
7781
  return out;
7683
7782
  })().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)}` }))
7783
+ (err) => mainRegions.map((r) => ({ id: cropId(r), pass: false, similarity: 0, inkRecall: 0, detail: `mount failed: ${describeMountError(err)}` }))
7685
7784
  );
7686
7785
  const mainResults = await raceMountDeadline(
7687
7786
  work,
@@ -7788,7 +7887,7 @@ body{margin:0;padding:20px}
7788
7887
  } finally {
7789
7888
  await page.close();
7790
7889
  }
7791
- })().catch((err) => ({ id: `composition:${mainSlug}`, pass: false, detail: `mount failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` }));
7890
+ })().catch((err) => ({ id: `composition:${mainSlug}`, pass: false, detail: `mount failed: ${describeMountError(err)}` }));
7792
7891
  const result = await raceMountDeadline(
7793
7892
  work,
7794
7893
  deadlineMs,
@@ -7849,7 +7948,7 @@ body{margin:0;padding:20px}
7849
7948
  } finally {
7850
7949
  await page.close();
7851
7950
  }
7852
- })().catch((err) => ({ id: `composition:${exp.pairKey}:renders`, pass: false, detail: `mount failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` }));
7951
+ })().catch((err) => ({ id: `composition:${exp.pairKey}:renders`, pass: false, detail: `mount failed: ${describeMountError(err)}` }));
7853
7952
  results.push(
7854
7953
  await raceMountDeadline(work, deadlineMs, () => ({ id: `composition:${exp.pairKey}:renders`, pass: false, detail: `mount deadline exceeded (${deadlineMs}ms)` }))
7855
7954
  );
@@ -7871,6 +7970,7 @@ var init_composition = __esm({
7871
7970
  init_candidate_css();
7872
7971
  init_font_faces();
7873
7972
  init_image_diff();
7973
+ init_mount_error();
7874
7974
  init_mount_limits();
7875
7975
  init_paths();
7876
7976
  REAL_JSX_SPEC = "__tendril_real_jsx__";
@@ -7890,17 +7990,17 @@ export const jsxs = (t, p, k) => real.jsxs(t, inject(t, p), k);
7890
7990
  });
7891
7991
 
7892
7992
  // packages/verify/src/occlusion.ts
7893
- import { existsSync as existsSync17 } from "node:fs";
7894
- import path23 from "node:path";
7993
+ import { existsSync as existsSync18 } from "node:fs";
7994
+ import path24 from "node:path";
7895
7995
  import { build as build7 } from "esbuild";
7896
7996
  import { chromium as chromium8 } from "playwright-core";
7897
7997
  async function compileTwoUp(task, bundleDir) {
7898
- const entryTsx = path23.join(bundleDir, task.entry);
7899
- if (!existsSync17(entryTsx)) return { error: `${task.entry} missing` };
7998
+ const entryTsx = path24.join(bundleDir, task.entry);
7999
+ if (!existsSync18(entryTsx)) return { error: `${task.entry} missing` };
7900
8000
  const src = `
7901
8001
  import { createElement } from "react";
7902
8002
  import { createRoot } from "react-dom/client";
7903
- import * as B from ${JSON.stringify(path23.resolve(entryTsx))};
8003
+ import * as B from ${JSON.stringify(path24.resolve(entryTsx))};
7904
8004
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
7905
8005
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
7906
8006
  for (const id of ["first", "second"]) {
@@ -8137,6 +8237,7 @@ var init_src5 = __esm({
8137
8237
  init_tasks();
8138
8238
  init_prelude();
8139
8239
  init_mount_limits();
8240
+ init_mount_error();
8140
8241
  init_font_collection();
8141
8242
  init_font_discovery();
8142
8243
  init_font_faces();
@@ -8151,23 +8252,23 @@ var init_src5 = __esm({
8151
8252
  });
8152
8253
 
8153
8254
  // packages/cli/src/environment.ts
8154
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "node:fs";
8155
- import path24 from "node:path";
8255
+ import { existsSync as existsSync19, readFileSync as readFileSync17 } from "node:fs";
8256
+ import path25 from "node:path";
8156
8257
  import { createHash as createHash5 } from "node:crypto";
8157
8258
  import { fileURLToPath as fileURLToPath5 } from "node:url";
8158
8259
  function cliVersion() {
8159
8260
  try {
8160
- return JSON.parse(readFileSync16(path24.join(path24.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
8261
+ return JSON.parse(readFileSync17(path25.join(path25.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
8161
8262
  } catch {
8162
8263
  return "dev";
8163
8264
  }
8164
8265
  }
8165
8266
  function environmentStamp(taskFamilies) {
8166
- const manifestPath2 = path24.join(fontCacheDir(), "manifest.json");
8267
+ const manifestPath2 = path25.join(fontCacheDir(), "manifest.json");
8167
8268
  let fontsHash = null;
8168
- if (existsSync18(manifestPath2)) {
8269
+ if (existsSync19(manifestPath2)) {
8169
8270
  try {
8170
- const entries = JSON.parse(readFileSync16(manifestPath2, "utf8"));
8271
+ const entries = JSON.parse(readFileSync17(manifestPath2, "utf8"));
8171
8272
  const scoped = taskFamilies === void 0 || taskFamilies === null ? entries : entries.filter((f) => taskFamilies.some((fam) => fam.toLowerCase() === f.family.toLowerCase()));
8172
8273
  const faces = scoped.map((f) => `${f.family}:${f.weight}:${f.sha256}`).sort();
8173
8274
  fontsHash = faces.length === 0 ? null : createHash5("sha256").update(faces.join("\n")).digest("hex").slice(0, 16);
@@ -8211,8 +8312,8 @@ var init_describe = __esm({
8211
8312
  });
8212
8313
 
8213
8314
  // packages/cli/src/env.ts
8214
- import { existsSync as existsSync19, readFileSync as readFileSync17 } from "node:fs";
8215
- import path25 from "node:path";
8315
+ import { existsSync as existsSync20, readFileSync as readFileSync18 } from "node:fs";
8316
+ import path26 from "node:path";
8216
8317
  function parseEnv(content) {
8217
8318
  const entries = /* @__PURE__ */ new Map();
8218
8319
  for (const line of content.split("\n")) {
@@ -8224,9 +8325,9 @@ function parseEnv(content) {
8224
8325
  function resolveCredential(name) {
8225
8326
  const fromProcess = process.env[name];
8226
8327
  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);
8328
+ const envPath = path26.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
8329
+ if (!existsSync20(envPath)) return void 0;
8330
+ return parseEnv(readFileSync18(envPath, "utf8")).get(name);
8230
8331
  }
8231
8332
  var init_env = __esm({
8232
8333
  "packages/cli/src/env.ts"() {
@@ -8286,16 +8387,16 @@ var init_output = __esm({
8286
8387
  });
8287
8388
 
8288
8389
  // 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";
8390
+ import { chmodSync, existsSync as existsSync21, mkdirSync as mkdirSync4, readFileSync as readFileSync19, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
8290
8391
  import os4 from "node:os";
8291
- import path26 from "node:path";
8392
+ import path27 from "node:path";
8292
8393
  function sessionPath() {
8293
- return process.env["TENDRIL_SESSION_PATH"] ?? path26.join(os4.homedir(), ".tendril", "session.json");
8394
+ return process.env["TENDRIL_SESSION_PATH"] ?? path27.join(os4.homedir(), ".tendril", "session.json");
8294
8395
  }
8295
8396
  function readStoredSession(file = sessionPath()) {
8296
- if (!existsSync20(file)) return void 0;
8397
+ if (!existsSync21(file)) return void 0;
8297
8398
  try {
8298
- const parsed = JSON.parse(readFileSync18(file, "utf8"));
8399
+ const parsed = JSON.parse(readFileSync19(file, "utf8"));
8299
8400
  if (typeof parsed.origin !== "string" || typeof parsed.token !== "string") return void 0;
8300
8401
  return { origin: parsed.origin, token: parsed.token };
8301
8402
  } catch {
@@ -8303,13 +8404,13 @@ function readStoredSession(file = sessionPath()) {
8303
8404
  }
8304
8405
  }
8305
8406
  function writeStoredSession(session, file = sessionPath()) {
8306
- mkdirSync4(path26.dirname(file), { recursive: true });
8307
- writeFileSync7(file, `${JSON.stringify(session, null, 2)}
8407
+ mkdirSync4(path27.dirname(file), { recursive: true });
8408
+ writeFileSync8(file, `${JSON.stringify(session, null, 2)}
8308
8409
  `, { mode: 384 });
8309
8410
  chmodSync(file, 384);
8310
8411
  }
8311
8412
  function clearStoredSession(file = sessionPath()) {
8312
- if (existsSync20(file)) rmSync3(file);
8413
+ if (existsSync21(file)) rmSync3(file);
8313
8414
  }
8314
8415
  function tokenFor(origin, file = sessionPath()) {
8315
8416
  const fromEnv = process.env["TENDRIL_TOKEN"];
@@ -8361,8 +8462,11 @@ var init_publish_client = __esm({
8361
8462
  this.token = options.token;
8362
8463
  this.send = options.fetch ?? globalThis.fetch;
8363
8464
  }
8364
- acceptTerms(input) {
8365
- return this.json("POST", "/api/consent", input);
8465
+ requestApproval(input) {
8466
+ return this.json("POST", "/api/publish-approvals", input);
8467
+ }
8468
+ pollApproval(input) {
8469
+ return this.json("GET", `/api/publish-approvals/${encodeURIComponent(input.approvalId)}`, null, true);
8366
8470
  }
8367
8471
  begin(input) {
8368
8472
  return this.json("POST", "/api/publications", input, true);
@@ -8456,7 +8560,6 @@ var init_publish_client = __esm({
8456
8560
  refusal: typeof record["refusal"] === "string" ? record["refusal"] : `the portal refused with ${String(response.status)}`,
8457
8561
  ...Array.isArray(record["detail"]) ? { detail: record["detail"] } : {},
8458
8562
  ...Array.isArray(record["missing"]) ? { missing: record["missing"] } : {},
8459
- ...isRecord(record["needsConsent"]) ? { needsConsent: record["needsConsent"] } : {},
8460
8563
  ...isRecord(record["needsConfirmation"]) ? { needsConfirmation: record["needsConfirmation"] } : {}
8461
8564
  };
8462
8565
  }
@@ -8468,17 +8571,17 @@ var init_publish_client = __esm({
8468
8571
  });
8469
8572
 
8470
8573
  // 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";
8574
+ import { chmodSync as chmodSync2, existsSync as existsSync22, mkdirSync as mkdirSync5, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "node:fs";
8472
8575
  import crypto from "node:crypto";
8473
8576
  import os5 from "node:os";
8474
- import path27 from "node:path";
8577
+ import path28 from "node:path";
8475
8578
  function entitlementPath() {
8476
- return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path27.join(os5.homedir(), ".tendril", "entitlement.json");
8579
+ return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path28.join(os5.homedir(), ".tendril", "entitlement.json");
8477
8580
  }
8478
8581
  function readStoredEntitlement(file = entitlementPath()) {
8479
- if (!existsSync21(file)) return void 0;
8582
+ if (!existsSync22(file)) return void 0;
8480
8583
  try {
8481
- const parsed = JSON.parse(readFileSync19(file, "utf8"));
8584
+ const parsed = JSON.parse(readFileSync20(file, "utf8"));
8482
8585
  if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
8483
8586
  return { token: parsed.token, lastRefreshAt: parsed.lastRefreshAt };
8484
8587
  } catch {
@@ -8486,8 +8589,8 @@ function readStoredEntitlement(file = entitlementPath()) {
8486
8589
  }
8487
8590
  }
8488
8591
  function writeStoredEntitlement(stored, file = entitlementPath()) {
8489
- mkdirSync5(path27.dirname(file), { recursive: true });
8490
- writeFileSync8(file, `${JSON.stringify(stored, null, 2)}
8592
+ mkdirSync5(path28.dirname(file), { recursive: true });
8593
+ writeFileSync9(file, `${JSON.stringify(stored, null, 2)}
8491
8594
  `);
8492
8595
  chmodSync2(file, 384);
8493
8596
  }
@@ -8571,9 +8674,9 @@ var init_entitlement = __esm({
8571
8674
 
8572
8675
  // packages/cli/src/commands/doctor.ts
8573
8676
  import { spawnSync } from "node:child_process";
8574
- import { existsSync as existsSync22, readFileSync as readFileSync20, readdirSync as readdirSync8 } from "node:fs";
8677
+ import { existsSync as existsSync23, readFileSync as readFileSync21, readdirSync as readdirSync8 } from "node:fs";
8575
8678
  import os6 from "node:os";
8576
- import path28 from "node:path";
8679
+ import path29 from "node:path";
8577
8680
  function withDeadline(work, ms) {
8578
8681
  return Promise.race([
8579
8682
  work,
@@ -8633,17 +8736,17 @@ async function runDoctorChecks(options) {
8633
8736
  remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
8634
8737
  });
8635
8738
  }
8636
- const fontManifest = path28.join(fontCacheDir(), "manifest.json");
8739
+ const fontManifest = path29.join(fontCacheDir(), "manifest.json");
8637
8740
  checks.push(
8638
- existsSync22(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync20(fontManifest, "utf8")).length} faces)` } : {
8741
+ existsSync23(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync21(fontManifest, "utf8")).length} faces)` } : {
8639
8742
  name: "font-cache",
8640
8743
  ok: true,
8641
8744
  detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
8642
8745
  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
8746
  }
8644
8747
  );
8645
- const pluginRoot = path28.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
8646
- if (existsSync22(pluginRoot)) {
8748
+ const pluginRoot = path29.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
8749
+ if (existsSync23(pluginRoot)) {
8647
8750
  try {
8648
8751
  const versions = readdirSync8(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
8649
8752
  const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
@@ -9915,8 +10018,8 @@ var init_engine_curated = __esm({
9915
10018
  });
9916
10019
 
9917
10020
  // 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";
10021
+ import { existsSync as existsSync25, mkdirSync as mkdirSync7, readFileSync as readFileSync23, renameSync, writeFileSync as writeFileSync11 } from "node:fs";
10022
+ import path32 from "node:path";
9920
10023
  import { z as z13 } from "zod";
9921
10024
  function objective(scores, behaviors) {
9922
10025
  const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
@@ -9958,9 +10061,9 @@ ${absentLines.join("\n")}` : ""}
9958
10061
  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
10062
  }
9960
10063
  function archivePriorRun(outDir) {
9961
- if (!existsSync24(path31.join(outDir, "run-log.json")) && !existsSync24(path31.join(outDir, "loop-state.json"))) return void 0;
10064
+ if (!existsSync25(path32.join(outDir, "run-log.json")) && !existsSync25(path32.join(outDir, "loop-state.json"))) return void 0;
9962
10065
  let n = 1;
9963
- while (existsSync24(`${outDir}-prev-${n}`)) n += 1;
10066
+ while (existsSync25(`${outDir}-prev-${n}`)) n += 1;
9964
10067
  renameSync(outDir, `${outDir}-prev-${n}`);
9965
10068
  return `${outDir}-prev-${n}`;
9966
10069
  }
@@ -9969,14 +10072,14 @@ async function runEngineLoop(opts) {
9969
10072
  const plateau = opts.plateau ?? 2;
9970
10073
  const progress = opts.onProgress ?? (() => {
9971
10074
  });
9972
- const statePath = path31.join(opts.outDir, "loop-state.json");
9973
- const resuming = opts.resume === true && existsSync24(statePath);
10075
+ const statePath = path32.join(opts.outDir, "loop-state.json");
10076
+ const resuming = opts.resume === true && existsSync25(statePath);
9974
10077
  if (!resuming) {
9975
10078
  const archived = archivePriorRun(opts.outDir);
9976
10079
  if (archived !== void 0) progress(`previous run archived to ${archived}`);
9977
10080
  }
9978
10081
  mkdirSync7(opts.outDir, { recursive: true });
9979
- const scratch = path31.join(opts.outDir, ".candidate");
10082
+ const scratch = path32.join(opts.outDir, ".candidate");
9980
10083
  let attempts = [];
9981
10084
  let log = [];
9982
10085
  let best;
@@ -9984,7 +10087,7 @@ async function runEngineLoop(opts) {
9984
10087
  let nonAccepted = 0;
9985
10088
  let stopReason = "max-iterations";
9986
10089
  if (resuming) {
9987
- const restored = LoopStateSchema.parse(JSON.parse(readFileSync22(statePath, "utf8")));
10090
+ const restored = LoopStateSchema.parse(JSON.parse(readFileSync23(statePath, "utf8")));
9988
10091
  attempts = restored.attempts;
9989
10092
  log = restored.iterations;
9990
10093
  spentUsd = restored.spentUsd;
@@ -9999,12 +10102,12 @@ async function runEngineLoop(opts) {
9999
10102
  progress(`resumed: ${log.length} iteration(s), $${spentUsd.toFixed(3)} spent, best pass=${best?.objective[0] ?? 0}`);
10000
10103
  }
10001
10104
  const persist = () => {
10002
- writeFileSync10(statePath, `${JSON.stringify({ version: 1, spentUsd, attempts, iterations: log }, null, 1)}
10105
+ writeFileSync11(statePath, `${JSON.stringify({ version: 1, spentUsd, attempts, iterations: log }, null, 1)}
10003
10106
  `);
10004
10107
  };
10005
10108
  const writeCandidate = (files) => {
10006
10109
  mkdirSync7(scratch, { recursive: true });
10007
- for (const [name, content] of Object.entries(files)) writeFileSync10(path31.join(scratch, name), content);
10110
+ for (const [name, content] of Object.entries(files)) writeFileSync11(path32.join(scratch, name), content);
10008
10111
  };
10009
10112
  const scoreCandidate = async (candidate, iter, usd, modelMs) => {
10010
10113
  writeCandidate(candidate.files);
@@ -10062,8 +10165,8 @@ async function runEngineLoop(opts) {
10062
10165
  const usd = candidate.usage?.usd ?? 0;
10063
10166
  spentUsd += usd;
10064
10167
  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);
10168
+ mkdirSync7(path32.join(opts.outDir, "responses"), { recursive: true });
10169
+ writeFileSync11(path32.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
10067
10170
  }
10068
10171
  if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
10069
10172
  const finish = candidate.usage?.finishReason ?? "?";
@@ -10089,10 +10192,10 @@ async function runEngineLoop(opts) {
10089
10192
  }
10090
10193
  }
10091
10194
  }
10092
- if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(path31.join(opts.outDir, name), content);
10195
+ if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync11(path32.join(opts.outDir, name), content);
10093
10196
  const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
10094
- writeFileSync10(
10095
- path31.join(opts.outDir, "run-log.json"),
10197
+ writeFileSync11(
10198
+ path32.join(opts.outDir, "run-log.json"),
10096
10199
  `${JSON.stringify(
10097
10200
  {
10098
10201
  ...opts.meta,
@@ -10159,8 +10262,8 @@ var init_loop2 = __esm({
10159
10262
  });
10160
10263
 
10161
10264
  // packages/generate/src/brief.ts
10162
- import { existsSync as existsSync25, readFileSync as readFileSync23 } from "node:fs";
10163
- import path32 from "node:path";
10265
+ import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
10266
+ import path33 from "node:path";
10164
10267
  import { PNG as PNG3 } from "pngjs";
10165
10268
  function singleAxes2(name) {
10166
10269
  const parsed = parseVariantAxes(name);
@@ -10600,15 +10703,15 @@ function authorBehaviors(api, extras = {}) {
10600
10703
  };
10601
10704
  }
10602
10705
  function envelopeText(file) {
10603
- return envelopeFirstTextPart(JSON.parse(readFileSync23(file, "utf8")));
10706
+ return envelopeFirstTextPart(JSON.parse(readFileSync24(file, "utf8")));
10604
10707
  }
10605
10708
  function metadataText(file) {
10606
- return envelopeTextContent(JSON.parse(readFileSync23(file, "utf8")));
10709
+ return envelopeTextContent(JSON.parse(readFileSync24(file, "utf8")));
10607
10710
  }
10608
10711
  function dismissEvidence(setDir, repSlugs) {
10609
10712
  for (const slug of repSlugs) {
10610
- const f = path32.join(setDir, slug, "get_design_context.json");
10611
- if (!existsSync25(f)) continue;
10713
+ const f = path33.join(setDir, slug, "get_design_context.json");
10714
+ if (!existsSync26(f)) continue;
10612
10715
  const text = envelopeText(f);
10613
10716
  const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*true\b/i.exec(text);
10614
10717
  if (propHit !== null) return { evidence: `emission prop "${propHit[1]}"`, visibleReps: [] };
@@ -10635,9 +10738,9 @@ function dismissEvidence(setDir, repSlugs) {
10635
10738
  const glyphIsTheComponent = (() => {
10636
10739
  const slugToCheck = vis.visibleIn[0];
10637
10740
  if (slugToCheck === void 0) return false;
10638
- const metaFile = path32.join(setDir, slugToCheck, "get_metadata.json");
10741
+ const metaFile = path33.join(setDir, slugToCheck, "get_metadata.json");
10639
10742
  try {
10640
- const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync23(metaFile, "utf8"))));
10743
+ const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync24(metaFile, "utf8"))));
10641
10744
  if (root.children.length !== 1) return false;
10642
10745
  const contains = (n) => normalizedLayerName(n.name) === norm2 || n.children.some(contains);
10643
10746
  return contains(root.children[0]);
@@ -10657,10 +10760,10 @@ function dismissEvidence(setDir, repSlugs) {
10657
10760
  return void 0;
10658
10761
  }
10659
10762
  function recordedReferencePng(setDir, slug) {
10660
- const f = path32.join(setDir, slug, "get_screenshot.json");
10661
- if (!existsSync25(f)) return void 0;
10763
+ const f = path33.join(setDir, slug, "get_screenshot.json");
10764
+ if (!existsSync26(f)) return void 0;
10662
10765
  try {
10663
- const env = JSON.parse(readFileSync23(f, "utf8")).content.find((c) => c.type === "image");
10766
+ const env = JSON.parse(readFileSync24(f, "utf8")).content.find((c) => c.type === "image");
10664
10767
  return env?.data === void 0 ? void 0 : Uint8Array.from(Buffer.from(env.data, "base64"));
10665
10768
  } catch {
10666
10769
  return void 0;
@@ -10740,13 +10843,13 @@ function recordedFontNeeds(setDir, opts = {}) {
10740
10843
  }
10741
10844
  }
10742
10845
  const manifest = loadManifest(setDir);
10743
- const setDefs = path32.join(setDir, "get_variable_defs.json");
10744
- if (existsSync25(setDefs)) fromDefs(envelopeText(setDefs));
10846
+ const setDefs = path33.join(setDir, "get_variable_defs.json");
10847
+ if (existsSync26(setDefs)) fromDefs(envelopeText(setDefs));
10745
10848
  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));
10849
+ const ctx = path33.join(setDir, rep.slug, "get_design_context.json");
10850
+ if (existsSync26(ctx)) fromEmission(envelopeText(ctx));
10851
+ const defs = path33.join(setDir, rep.slug, "get_variable_defs.json");
10852
+ if (existsSync26(defs)) fromDefs(envelopeText(defs));
10750
10853
  }
10751
10854
  return [...byFamily.entries()].map(([family, paired]) => {
10752
10855
  const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
@@ -10757,10 +10860,10 @@ function symbolFontGlyphCount(setDir, reps) {
10757
10860
  const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
10758
10861
  const glyphs = /* @__PURE__ */ new Set();
10759
10862
  for (const rep of reps) {
10760
- const file = path32.join(setDir, rep, "get_metadata.json");
10761
- if (!existsSync25(file)) continue;
10863
+ const file = path33.join(setDir, rep, "get_metadata.json");
10864
+ if (!existsSync26(file)) continue;
10762
10865
  try {
10763
- const text = JSON.parse(readFileSync23(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
10866
+ const text = JSON.parse(readFileSync24(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
10764
10867
  for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
10765
10868
  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
10869
  if (PUA.test(name)) glyphs.add(name);
@@ -10786,8 +10889,8 @@ function recordedTextSlots(setDir, repSlugs) {
10786
10889
  const propRep = [];
10787
10890
  const perRep = [];
10788
10891
  for (const slug of repSlugs) {
10789
- const f = path32.join(setDir, slug, "get_design_context.json");
10790
- if (!existsSync25(f)) continue;
10892
+ const f = path33.join(setDir, slug, "get_design_context.json");
10893
+ if (!existsSync26(f)) continue;
10791
10894
  const code = envelopeText(f);
10792
10895
  const props = /* @__PURE__ */ new Map();
10793
10896
  for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
@@ -10813,8 +10916,8 @@ function recordedTextSlots(setDir, repSlugs) {
10813
10916
  const axisValuesBySlug = /* @__PURE__ */ new Map();
10814
10917
  const valuesByAxis = /* @__PURE__ */ new Map();
10815
10918
  for (const slug of repSlugs) {
10816
- const metaFile = path32.join(setDir, slug, "get_metadata.json");
10817
- if (!existsSync25(metaFile)) continue;
10919
+ const metaFile = path33.join(setDir, slug, "get_metadata.json");
10920
+ if (!existsSync26(metaFile)) continue;
10818
10921
  const name = symbolName(metadataText(metaFile));
10819
10922
  if (name === void 0) continue;
10820
10923
  const values = /* @__PURE__ */ new Set();
@@ -10929,8 +11032,8 @@ function authorTaskFromSet(setDir, opts = {}) {
10929
11032
  const poses = [];
10930
11033
  const missing = [];
10931
11034
  for (const rep of manifest.reps) {
10932
- const metaFile = path32.join(setDir, rep.slug, "get_metadata.json");
10933
- if (!existsSync25(metaFile)) {
11035
+ const metaFile = path33.join(setDir, rep.slug, "get_metadata.json");
11036
+ if (!existsSync26(metaFile)) {
10934
11037
  missing.push(rep.slug);
10935
11038
  continue;
10936
11039
  }
@@ -10944,8 +11047,8 @@ function authorTaskFromSet(setDir, opts = {}) {
10944
11047
  if (missing.length > 0) {
10945
11048
  throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
10946
11049
  }
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);
11050
+ const setMeta = path33.join(setDir, "get_metadata.json");
11051
+ const latticeNames = manifest.latticeNames ?? (existsSync26(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
10949
11052
  const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
10950
11053
  const recordedFonts = recordedFontFamilies(setDir);
10951
11054
  const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
@@ -11146,17 +11249,17 @@ var init_brief = __esm({
11146
11249
  });
11147
11250
 
11148
11251
  // 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";
11252
+ import { existsSync as existsSync27, readFileSync as readFileSync25, readdirSync as readdirSync10 } from "node:fs";
11253
+ import path34 from "node:path";
11151
11254
  function repText(set, rep, tool) {
11152
- const env = JSON.parse(readFileSync24(path33.join(set, rep, `${tool}.json`), "utf8"));
11255
+ const env = JSON.parse(readFileSync25(path34.join(set, rep, `${tool}.json`), "utf8"));
11153
11256
  return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
11154
11257
  }
11155
11258
  function refPngDims(set, rep) {
11156
- const f = path33.join(set, rep, "get_screenshot.json");
11157
- if (!existsSync26(f)) return void 0;
11259
+ const f = path34.join(set, rep, "get_screenshot.json");
11260
+ if (!existsSync27(f)) return void 0;
11158
11261
  try {
11159
- const env = JSON.parse(readFileSync24(f, "utf8")).content.find((c) => c.type === "image");
11262
+ const env = JSON.parse(readFileSync25(f, "utf8")).content.find((c) => c.type === "image");
11160
11263
  if (env?.data === void 0) return void 0;
11161
11264
  const buf = Buffer.from(env.data, "base64");
11162
11265
  if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return void 0;
@@ -11222,20 +11325,20 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
11222
11325
  }
11223
11326
  function buildSegments(task, mode = "fenced") {
11224
11327
  const SET = task.set;
11225
- let defsRecorded = existsSync26(path33.join(SET, "get_variable_defs.json"));
11328
+ let defsRecorded = existsSync27(path34.join(SET, "get_variable_defs.json"));
11226
11329
  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"))) || "{}";
11330
+ if (existsSync27(path34.join(SET, "get_variable_defs.json"))) {
11331
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync25(path34.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
11229
11332
  try {
11230
11333
  rawDefs = JSON.parse(text);
11231
11334
  } catch {
11232
11335
  }
11233
11336
  } else {
11234
11337
  for (const cfg of task.configs) {
11235
- const f = path33.join(SET, cfg.rep, "get_variable_defs.json");
11236
- if (!existsSync26(f)) continue;
11338
+ const f = path34.join(SET, cfg.rep, "get_variable_defs.json");
11339
+ if (!existsSync27(f)) continue;
11237
11340
  defsRecorded = true;
11238
- const text = envelopeFirstTextPart(JSON.parse(readFileSync24(f, "utf8"))) || "{}";
11341
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync25(f, "utf8"))) || "{}";
11239
11342
  try {
11240
11343
  for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
11241
11344
  } catch {
@@ -11243,8 +11346,8 @@ function buildSegments(task, mode = "fenced") {
11243
11346
  }
11244
11347
  }
11245
11348
  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"))) : "";
11349
+ const f = path34.join(SET, cfg.rep, "get_design_context.json");
11350
+ return existsSync27(f) ? envelopeFirstTextPart(JSON.parse(readFileSync25(f, "utf8"))) : "";
11248
11351
  });
11249
11352
  const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
11250
11353
  const defs = JSON.stringify(map, null, 1);
@@ -11262,9 +11365,9 @@ TOKEN MAP NEVER RECORDED: this set holds no get_variable_defs envelope, so wheth
11262
11365
  for (const cfg of task.configs) {
11263
11366
  const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
11264
11367
  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}:
11368
+ const assets = readdirSync10(path34.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
11266
11369
  \`\`\`svg
11267
- ${readFileSync24(path33.join(SET, cfg.rep, f), "utf8")}
11370
+ ${readFileSync25(path34.join(SET, cfg.rep, f), "utf8")}
11268
11371
  \`\`\``).join("\n");
11269
11372
  const refNote = (() => {
11270
11373
  const dims = refPngDims(SET, cfg.rep);
@@ -11300,7 +11403,7 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
11300
11403
  } else {
11301
11404
  parts.push(`
11302
11405
  ## 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.`);
11406
+ 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
11407
  }
11305
11408
  return parts.join("\n");
11306
11409
  }
@@ -11368,8 +11471,8 @@ var init_adapter = __esm({
11368
11471
 
11369
11472
  // packages/generate/src/bundle-emit.ts
11370
11473
  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";
11474
+ import { copyFileSync, existsSync as existsSync28, mkdirSync as mkdirSync8, readFileSync as readFileSync26, readdirSync as readdirSync11, rmSync as rmSync4, writeFileSync as writeFileSync12 } from "node:fs";
11475
+ import path35 from "node:path";
11373
11476
  function pinFromConfigs(configs) {
11374
11477
  const domains = /* @__PURE__ */ new Map();
11375
11478
  const kinds = /* @__PURE__ */ new Map();
@@ -11438,9 +11541,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11438
11541
  const notices = [];
11439
11542
  const licenseTexts = /* @__PURE__ */ new Map();
11440
11543
  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";
11544
+ const src = path35.join(cacheDir, path35.basename(face.file));
11545
+ const target = `./fonts/${path35.basename(face.file)}`;
11546
+ const format = FONT_FORMATS[path35.extname(face.file).toLowerCase()] ?? "truetype";
11444
11547
  const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
11445
11548
  const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
11446
11549
  const license = normalizeFontLicense(face.license);
@@ -11478,14 +11581,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11478
11581
  `/* ${decl} */`
11479
11582
  );
11480
11583
  }
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)));
11584
+ } else if (existsSync28(src) && createHash6("sha256").update(readFileSync26(src)).digest("hex") === face.sha256) {
11585
+ mkdirSync8(path35.join(bundleDir, "fonts"), { recursive: true });
11586
+ copyFileSync(src, path35.join(bundleDir, "fonts", path35.basename(face.file)));
11484
11587
  licenseTexts.set(terms.file, terms.text);
11485
11588
  const upstream = upstreamAttribution(face);
11486
11589
  notices.push(
11487
11590
  "",
11488
- `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path34.basename(face.file)}`,
11591
+ `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path35.basename(face.file)}`,
11489
11592
  ` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
11490
11593
  ` source: ${face.source}`,
11491
11594
  ` sha256: ${face.sha256}`,
@@ -11499,9 +11602,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11499
11602
  }
11500
11603
  if (lines.length === 0) return null;
11501
11604
  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")}
11605
+ const fontsDir = path35.join(bundleDir, "fonts");
11606
+ for (const [file, text] of licenseTexts) writeFileSync12(path35.join(fontsDir, file), text);
11607
+ writeFileSync12(path35.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
11505
11608
  `);
11506
11609
  header.push(
11507
11610
  "/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
@@ -11513,10 +11616,10 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11513
11616
  `;
11514
11617
  }
11515
11618
  function countLatticeSymbols(setDir) {
11516
- const manifestFile = path34.join(setDir, "recording-set.json");
11517
- if (existsSync27(manifestFile)) {
11619
+ const manifestFile = path35.join(setDir, "recording-set.json");
11620
+ if (existsSync28(manifestFile)) {
11518
11621
  try {
11519
- const stored = JSON.parse(readFileSync25(manifestFile, "utf8"));
11622
+ const stored = JSON.parse(readFileSync26(manifestFile, "utf8"));
11520
11623
  if (stored.variantScope !== "component-set") return null;
11521
11624
  const lattice = stored.latticeNames;
11522
11625
  if (lattice !== void 0 && lattice.length > 0) return lattice.length;
@@ -11524,13 +11627,13 @@ function countLatticeSymbols(setDir) {
11524
11627
  }
11525
11628
  }
11526
11629
  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));
11630
+ path35.join(setDir, "get_metadata.json"),
11631
+ ...existsSync28(setDir) ? readdirSync11(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path35.join(setDir, f)) : []
11632
+ ].filter((f) => existsSync28(f));
11530
11633
  if (files.length === 0) return null;
11531
11634
  let count = 0;
11532
11635
  for (const f of files) {
11533
- const text = envelopeTextContent(JSON.parse(readFileSync25(f, "utf8")));
11636
+ const text = envelopeTextContent(JSON.parse(readFileSync26(f, "utf8")));
11534
11637
  count += [...text.matchAll(/name="([^"]*)"/g)].filter((m) => m[1].includes("=")).length;
11535
11638
  }
11536
11639
  return count > 0 ? count : null;
@@ -11538,21 +11641,21 @@ function countLatticeSymbols(setDir) {
11538
11641
  function recordingSetHash(setDir, configs) {
11539
11642
  const relPaths = [];
11540
11643
  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);
11644
+ if (existsSync28(path35.join(setDir, name))) relPaths.push(name);
11542
11645
  }
11543
11646
  for (const cfg of configs) {
11544
11647
  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}`);
11648
+ if (existsSync28(path35.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
11546
11649
  }
11547
- if (existsSync27(path34.join(setDir, cfg.rep))) {
11548
- for (const asset of readdirSync11(path34.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
11650
+ if (existsSync28(path35.join(setDir, cfg.rep))) {
11651
+ for (const asset of readdirSync11(path35.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
11549
11652
  relPaths.push(`${cfg.rep}/${asset}`);
11550
11653
  }
11551
11654
  }
11552
11655
  }
11553
11656
  return hashRecordingSet(
11554
11657
  relPaths,
11555
- (p) => new Uint8Array(readFileSync25(path34.join(setDir, p))),
11658
+ (p) => new Uint8Array(readFileSync26(path35.join(setDir, p))),
11556
11659
  (chunks) => {
11557
11660
  const h = createHash6("sha256");
11558
11661
  for (const c of chunks) h.update(c);
@@ -11593,8 +11696,8 @@ function emitBundleV1(opts) {
11593
11696
  const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
11594
11697
  const parity = opts.behaviors.filter((b) => b.id.startsWith("parity:"));
11595
11698
  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"));
11699
+ const cssFiles = ["styles.css", "tokens.css"].map((f) => path35.join(opts.bundleDir, f)).filter((f) => existsSync28(f));
11700
+ const families = cssFontFamilies(cssFiles.map((f) => readFileSync26(f, "utf8")).join("\n"));
11598
11701
  const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
11599
11702
  family: f.family,
11600
11703
  weight: f.weight,
@@ -11623,7 +11726,7 @@ function emitBundleV1(opts) {
11623
11726
  // resolvable via verify's --set override).
11624
11727
  path: (() => {
11625
11728
  const base = process.env["INIT_CWD"] ?? process.cwd();
11626
- const rel = path34.relative(base, opts.task.set);
11729
+ const rel = path35.relative(base, opts.task.set);
11627
11730
  return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
11628
11731
  })(),
11629
11732
  component: opts.componentName,
@@ -11660,25 +11763,25 @@ function emitBundleV1(opts) {
11660
11763
  })
11661
11764
  };
11662
11765
  const written = [];
11663
- const manifestPath2 = path34.join(opts.bundleDir, "component.json");
11664
- writeFileSync11(manifestPath2, `${JSON.stringify(manifest, null, 2)}
11766
+ const manifestPath2 = path35.join(opts.bundleDir, "component.json");
11767
+ writeFileSync12(manifestPath2, `${JSON.stringify(manifest, null, 2)}
11665
11768
  `);
11666
11769
  written.push(manifestPath2);
11667
- const stylesPath = path34.join(opts.bundleDir, "styles.css");
11668
- if (existsSync27(stylesPath)) {
11770
+ const stylesPath = path35.join(opts.bundleDir, "styles.css");
11771
+ if (existsSync28(stylesPath)) {
11669
11772
  const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
11670
- const current = readFileSync25(stylesPath, "utf8");
11773
+ const current = readFileSync26(stylesPath, "utf8");
11671
11774
  const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
11672
- writeFileSync11(stylesPath, `${comment}
11775
+ writeFileSync12(stylesPath, `${comment}
11673
11776
  ${stripped}`);
11674
11777
  written.push(stylesPath);
11675
11778
  }
11676
- const fontsCssPath = path34.join(opts.bundleDir, "fonts.css");
11677
- rmSync4(path34.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
11779
+ const fontsCssPath = path35.join(opts.bundleDir, "fonts.css");
11780
+ rmSync4(path35.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
11678
11781
  rmSync4(fontsCssPath, { force: true });
11679
11782
  const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
11680
11783
  if (fontsCss !== null) {
11681
- writeFileSync11(fontsCssPath, fontsCss);
11784
+ writeFileSync12(fontsCssPath, fontsCss);
11682
11785
  written.push(fontsCssPath);
11683
11786
  }
11684
11787
  const unrecorded = lattice === null ? null : Math.max(0, lattice - statuses.length);
@@ -12107,8 +12210,8 @@ DEALINGS IN THE FONT SOFTWARE.
12107
12210
 
12108
12211
  // packages/generate/src/compose-pins.ts
12109
12212
  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";
12213
+ import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync12, realpathSync as realpathSync3, statSync as statSync4 } from "node:fs";
12214
+ import path36 from "node:path";
12112
12215
  function bundleDirs(roots, depth = 4) {
12113
12216
  const found = [];
12114
12217
  const seen = /* @__PURE__ */ new Set();
@@ -12117,11 +12220,11 @@ function bundleDirs(roots, depth = 4) {
12117
12220
  try {
12118
12221
  key = realpathSync3(dir);
12119
12222
  } catch {
12120
- key = path35.resolve(dir);
12223
+ key = path36.resolve(dir);
12121
12224
  }
12122
12225
  if (seen.has(key)) return;
12123
12226
  seen.add(key);
12124
- if (existsSync28(path35.join(dir, "component.json"))) {
12227
+ if (existsSync29(path36.join(dir, "component.json"))) {
12125
12228
  found.push(key);
12126
12229
  return;
12127
12230
  }
@@ -12134,14 +12237,14 @@ function bundleDirs(roots, depth = 4) {
12134
12237
  }
12135
12238
  for (const e of entries) {
12136
12239
  if (e === "node_modules" || e.startsWith(".")) continue;
12137
- const full = path35.join(dir, e);
12240
+ const full = path36.join(dir, e);
12138
12241
  try {
12139
12242
  if (statSync4(full).isDirectory()) walk2(full, remaining - 1);
12140
12243
  } catch {
12141
12244
  }
12142
12245
  }
12143
12246
  };
12144
- for (const r of roots) walk2(path35.resolve(r), depth);
12247
+ for (const r of roots) walk2(path36.resolve(r), depth);
12145
12248
  return found;
12146
12249
  }
12147
12250
  function composedPins(hostSet, libraryRoots) {
@@ -12160,7 +12263,7 @@ function composedPins(hostSet, libraryRoots) {
12160
12263
  let pinned = false;
12161
12264
  const failures = [];
12162
12265
  for (const rel of partnerRels) {
12163
- const partnerSet = path35.resolve(hostSet, rel);
12266
+ const partnerSet = path36.resolve(hostSet, rel);
12164
12267
  let partnerTask;
12165
12268
  let partnerManifest;
12166
12269
  try {
@@ -12189,7 +12292,7 @@ function composedPins(hostSet, libraryRoots) {
12189
12292
  const wantHash = recordingSetHash(partnerSet, partnerTask.configs);
12190
12293
  const matches = candidates.filter((dir) => {
12191
12294
  try {
12192
- const parsed = readBundleManifest(readFileSync26(path35.join(dir, "component.json"), "utf8"));
12295
+ const parsed = readBundleManifest(readFileSync27(path36.join(dir, "component.json"), "utf8"));
12193
12296
  return parsed.manifest?.provenance.recordingSet.hash === wantHash;
12194
12297
  } catch {
12195
12298
  return false;
@@ -12202,13 +12305,13 @@ function composedPins(hostSet, libraryRoots) {
12202
12305
  continue;
12203
12306
  }
12204
12307
  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`);
12308
+ 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
12309
  continue;
12207
12310
  }
12208
12311
  const bundleDir = matches[0];
12209
12312
  let manifest;
12210
12313
  try {
12211
- manifest = readBundleManifest(readFileSync26(path35.join(bundleDir, "component.json"), "utf8")).manifest;
12314
+ manifest = readBundleManifest(readFileSync27(path36.join(bundleDir, "component.json"), "utf8")).manifest;
12212
12315
  } catch {
12213
12316
  manifest = void 0;
12214
12317
  }
@@ -12225,8 +12328,8 @@ function composedPins(hostSet, libraryRoots) {
12225
12328
  const moduleFiles = [];
12226
12329
  let fileIssue;
12227
12330
  for (const name of [manifest.entry, "styles.css", "tokens.css", "fonts.css"]) {
12228
- const file = path35.join(bundleDir, name);
12229
- if (!existsSync28(file)) {
12331
+ const file = path36.join(bundleDir, name);
12332
+ if (!existsSync29(file)) {
12230
12333
  if (name === manifest.entry || name === "styles.css") {
12231
12334
  fileIssue = `${rel}: partner bundle is missing ${name}`;
12232
12335
  break;
@@ -12235,7 +12338,7 @@ function composedPins(hostSet, libraryRoots) {
12235
12338
  }
12236
12339
  let bytes;
12237
12340
  try {
12238
- bytes = readFileSync26(file);
12341
+ bytes = readFileSync27(file);
12239
12342
  } catch {
12240
12343
  fileIssue = `${rel}: partner file ${name} at ${bundleDir} is unreadable`;
12241
12344
  break;
@@ -12307,14 +12410,14 @@ function composedChecks(candidateDir, hostEntry, pins) {
12307
12410
  const checks = [];
12308
12411
  let entrySource = "";
12309
12412
  try {
12310
- entrySource = readFileSync26(path35.join(candidateDir, hostEntry), "utf8");
12413
+ entrySource = readFileSync27(path36.join(candidateDir, hostEntry), "utf8");
12311
12414
  } catch {
12312
12415
  }
12313
- const candidateRoot = path35.resolve(candidateDir);
12416
+ const candidateRoot = path36.resolve(candidateDir);
12314
12417
  for (const pin of pins) {
12315
12418
  const dir = composedModuleDir(pin.partnerName);
12316
- const resolvedDir = path35.resolve(candidateDir, dir);
12317
- if (!resolvedDir.startsWith(candidateRoot + path35.sep)) {
12419
+ const resolvedDir = path36.resolve(candidateDir, dir);
12420
+ if (!resolvedDir.startsWith(candidateRoot + path36.sep)) {
12318
12421
  checks.push({ id: `composition:${pin.pairKey}:module-verbatim`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
12319
12422
  checks.push({ id: `composition:${pin.pairKey}:imported`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
12320
12423
  continue;
@@ -12325,12 +12428,12 @@ function composedChecks(candidateDir, hostEntry, pins) {
12325
12428
  wrong.push(`${f.name} refused (not a plain path segment)`);
12326
12429
  continue;
12327
12430
  }
12328
- const target = path35.join(candidateDir, dir, f.name);
12329
- if (!existsSync28(target)) {
12431
+ const target = path36.join(candidateDir, dir, f.name);
12432
+ if (!existsSync29(target)) {
12330
12433
  wrong.push(`${f.name} missing`);
12331
12434
  continue;
12332
12435
  }
12333
- const sha = createHash7("sha256").update(readFileSync26(target)).digest("hex");
12436
+ const sha = createHash7("sha256").update(readFileSync27(target)).digest("hex");
12334
12437
  if (sha !== f.sha256) wrong.push(`${f.name} differs from the pinned partner bytes`);
12335
12438
  }
12336
12439
  checks.push({
@@ -12355,10 +12458,10 @@ function rootClassesFor(emission, nodeId) {
12355
12458
  }
12356
12459
  function regionOverrides(hostSet, partnerSet, instances) {
12357
12460
  const read = (setDir, rep) => {
12358
- const f = path35.join(setDir, rep, "get_design_context.json");
12359
- if (!existsSync28(f)) return void 0;
12461
+ const f = path36.join(setDir, rep, "get_design_context.json");
12462
+ if (!existsSync29(f)) return void 0;
12360
12463
  try {
12361
- return envelopeFirstTextPart(JSON.parse(readFileSync26(f, "utf8")));
12464
+ return envelopeFirstTextPart(JSON.parse(readFileSync27(f, "utf8")));
12362
12465
  } catch {
12363
12466
  return void 0;
12364
12467
  }
@@ -12388,7 +12491,7 @@ var init_compose_pins = __esm({
12388
12491
  init_src4();
12389
12492
  init_brief();
12390
12493
  init_bundle_emit();
12391
- composedModuleDir = (partnerName) => path35.posix.join("composed", partnerName);
12494
+ composedModuleDir = (partnerName) => path36.posix.join("composed", partnerName);
12392
12495
  safeSegment = (s) => /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(s) && !s.includes("..");
12393
12496
  MAX_PINNED_FILE_BYTES = 1024 * 1024;
12394
12497
  CONTEXT_LAYOUT_TOKENS = /* @__PURE__ */ new Set(["shrink-0", "grow", "flex-1", "basis-0", "self-stretch", "w-full", "h-full"]);
@@ -12396,8 +12499,8 @@ var init_compose_pins = __esm({
12396
12499
  });
12397
12500
 
12398
12501
  // 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";
12502
+ import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync13, statSync as statSync5 } from "node:fs";
12503
+ import path37 from "node:path";
12401
12504
  function springProgress(u, bounce) {
12402
12505
  const decay = Math.log(100);
12403
12506
  if (bounce <= 0) {
@@ -12468,10 +12571,10 @@ function reportsNoMotion(text) {
12468
12571
  });
12469
12572
  }
12470
12573
  function motionTruthFor(setDir) {
12471
- const file = path36.join(setDir, "get_motion_context.json");
12472
- if (existsSync29(file) && usableEnvelope(file, "get_motion_context").ok) {
12574
+ const file = path37.join(setDir, "get_motion_context.json");
12575
+ if (existsSync30(file) && usableEnvelope(file, "get_motion_context").ok) {
12473
12576
  try {
12474
- const text = envelopeTextContent(JSON.parse(readFileSync27(file, "utf8")));
12577
+ const text = envelopeTextContent(JSON.parse(readFileSync28(file, "utf8")));
12475
12578
  if (text.trim() === "") return { state: "recorded-empty" };
12476
12579
  return reportsNoMotion(text) ? { state: "recorded-no-motion", text } : { state: "recorded", text };
12477
12580
  } catch {
@@ -12484,21 +12587,21 @@ function motionTruthFor(setDir) {
12484
12587
  }
12485
12588
  }
12486
12589
  function motionDisclosure(bundleDir, setDir) {
12487
- const sheets = ["styles.css", "tokens.css"].map((f) => path36.join(bundleDir, f));
12488
- const composedRoot = path36.join(bundleDir, "composed");
12590
+ const sheets = ["styles.css", "tokens.css"].map((f) => path37.join(bundleDir, f));
12591
+ const composedRoot = path37.join(bundleDir, "composed");
12489
12592
  try {
12490
12593
  for (const entry of readdirSync13(composedRoot).sort()) {
12491
- const dir = path36.join(composedRoot, entry);
12594
+ const dir = path37.join(composedRoot, entry);
12492
12595
  try {
12493
12596
  if (!statSync5(dir).isDirectory()) continue;
12494
12597
  } catch {
12495
12598
  continue;
12496
12599
  }
12497
- sheets.push(path36.join(dir, "styles.css"), path36.join(dir, "tokens.css"));
12600
+ sheets.push(path37.join(dir, "styles.css"), path37.join(dir, "tokens.css"));
12498
12601
  }
12499
12602
  } catch {
12500
12603
  }
12501
- const css = sheets.filter((f) => existsSync29(f)).map((f) => readFileSync27(f, "utf8")).join("\n");
12604
+ const css = sheets.filter((f) => existsSync30(f)).map((f) => readFileSync28(f, "utf8")).join("\n");
12502
12605
  if (!MOTION_CSS_PATTERN.test(scannableCss(css))) return { present: false };
12503
12606
  return {
12504
12607
  present: true,
@@ -12865,12 +12968,12 @@ var init_components = __esm({
12865
12968
 
12866
12969
  // packages/generate/src/codebase/walk.ts
12867
12970
  import fs2 from "node:fs";
12868
- import path37 from "node:path";
12971
+ import path38 from "node:path";
12869
12972
  function resolvedPathIsExcluded(real, roots) {
12870
- if (isNeverRead(path37.basename(real))) return true;
12973
+ if (isNeverRead(path38.basename(real))) return true;
12871
12974
  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)) {
12975
+ if (real !== root && !real.startsWith(root + path38.sep)) continue;
12976
+ for (const segment of path38.relative(root, real).split(path38.sep).slice(0, -1)) {
12874
12977
  if (segment.startsWith(".") || EXCLUDED_DIRS.has(segment)) return true;
12875
12978
  }
12876
12979
  }
@@ -12884,7 +12987,7 @@ function containedRealpath(abs, roots) {
12884
12987
  return null;
12885
12988
  }
12886
12989
  for (const root of roots) {
12887
- if (real === root || real.startsWith(root + path37.sep)) return real;
12990
+ if (real === root || real.startsWith(root + path38.sep)) return real;
12888
12991
  }
12889
12992
  return null;
12890
12993
  }
@@ -12917,7 +13020,7 @@ function walkRepo(roots, limits, accept) {
12917
13020
  continue;
12918
13021
  }
12919
13022
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
12920
- const abs = path37.join(frame.dir, entry.name);
13023
+ const abs = path38.join(frame.dir, entry.name);
12921
13024
  if (EXCLUDED_DIRS.has(entry.name)) continue;
12922
13025
  if (isNeverRead(entry.name)) continue;
12923
13026
  const real = containedRealpath(abs, realRoots);
@@ -13005,18 +13108,18 @@ var init_walk = __esm({
13005
13108
  /^\.netrc$/i
13006
13109
  ];
13007
13110
  isNeverRead = (basename) => NEVER_READ.some((re) => re.test(basename));
13008
- toRel = (root, abs) => path37.relative(root, abs).split(path37.sep).join(path37.posix.sep);
13111
+ toRel = (root, abs) => path38.relative(root, abs).split(path38.sep).join(path38.posix.sep);
13009
13112
  }
13010
13113
  });
13011
13114
 
13012
13115
  // packages/generate/src/codebase/scan.ts
13013
13116
  import crypto2 from "node:crypto";
13014
13117
  import fs3 from "node:fs";
13015
- import path38 from "node:path";
13118
+ import path39 from "node:path";
13016
13119
  import postcss3 from "postcss";
13017
13120
  function scanCodebase(options) {
13018
13121
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
13019
- const roots = options.roots.map((r) => path38.resolve(r));
13122
+ const roots = options.roots.map((r) => path39.resolve(r));
13020
13123
  const walk2 = walkRepo(
13021
13124
  roots,
13022
13125
  {
@@ -13036,7 +13139,7 @@ function scanCodebase(options) {
13036
13139
  let bytesRead = 0;
13037
13140
  let filesRead = 0;
13038
13141
  for (const file of walk2.files) {
13039
- const base = path38.posix.basename(file.rel);
13142
+ const base = path39.posix.basename(file.rel);
13040
13143
  configFiles.add(file.rel);
13041
13144
  if (/^tailwind\.config\./.test(base) || file.rel === "babel.config.js") continue;
13042
13145
  const text = readTextFile(file.abs);
@@ -13052,7 +13155,7 @@ function scanCodebase(options) {
13052
13155
  }
13053
13156
  const css = extractCssCustomProperties(cssFiles.filter((f) => !f.rel.includes("..")));
13054
13157
  const components = scanComponents(componentFiles);
13055
- const packages = manifests.filter((m) => path38.posix.basename(m.rel) === "package.json");
13158
+ const packages = manifests.filter((m) => path39.posix.basename(m.rel) === "package.json");
13056
13159
  const styling = detectStyling(configFiles, cssFiles, componentFiles, manifests);
13057
13160
  const classNameStyle = representativeClassNames(cssFiles, css.unparsed.length);
13058
13161
  const disclosures = buildDisclosures(
@@ -13095,13 +13198,13 @@ function scanCodebase(options) {
13095
13198
  },
13096
13199
  components: {
13097
13200
  entries: components.entries.slice(0, PROFILE_LIMITS.maxComponents),
13098
- fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path38.posix.basename(f.rel)))),
13201
+ fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path39.posix.basename(f.rel)))),
13099
13202
  directoryLayout: buildHistogram(componentFiles.map((f) => classifyDirectoryLayout(f.rel))),
13100
13203
  exportStyle: buildHistogram(components.entries.map((e) => e.exportStyle)),
13101
13204
  classNameStyle,
13102
13205
  colocation: buildHistogram(collectColocation(componentFiles, cssFiles)),
13103
13206
  barrelFiles: componentFiles.filter(
13104
- (f) => /^index\.[tj]sx?$/.test(path38.posix.basename(f.rel)) && isReExportOnly(f.text)
13207
+ (f) => /^index\.[tj]sx?$/.test(path39.posix.basename(f.rel)) && isReExportOnly(f.text)
13105
13208
  ).length,
13106
13209
  refForwarding: {
13107
13210
  forwardRef: componentFiles.filter((f) => /\bforwardRef\s*[(<]/.test(f.text)).length,
@@ -13124,7 +13227,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
13124
13227
  };
13125
13228
  const deps = /* @__PURE__ */ new Map();
13126
13229
  for (const manifest of manifests) {
13127
- if (path38.posix.basename(manifest.rel) !== "package.json") continue;
13230
+ if (path39.posix.basename(manifest.rel) !== "package.json") continue;
13128
13231
  try {
13129
13232
  const parsed = JSON.parse(manifest.text);
13130
13233
  for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
@@ -13136,7 +13239,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
13136
13239
  }
13137
13240
  }
13138
13241
  for (const cfg of configFiles) {
13139
- const base = path38.posix.basename(cfg);
13242
+ const base = path39.posix.basename(cfg);
13140
13243
  if (/^tailwind\.config\./.test(base)) add("tailwind-v3", "file", cfg);
13141
13244
  if (base === "components.json") add("shadcn-style", "file", cfg);
13142
13245
  }
@@ -13194,8 +13297,8 @@ function collectClassNames(cssFiles) {
13194
13297
  return [...distinct].sort().map(classifyClassName);
13195
13298
  }
13196
13299
  function classifyDirectoryLayout(rel) {
13197
- const base = path38.posix.basename(rel).replace(/\.[^.]+$/, "");
13198
- const dir = path38.posix.basename(path38.posix.dirname(rel));
13300
+ const base = path39.posix.basename(rel).replace(/\.[^.]+$/, "");
13301
+ const dir = path39.posix.basename(path39.posix.dirname(rel));
13199
13302
  if (base === "index") return "component-dir";
13200
13303
  if (base === dir) return "component-dir";
13201
13304
  return "flat-file";
@@ -13219,7 +13322,7 @@ function detectFormatting(manifests, componentFiles) {
13219
13322
  (a, b) => a.rel.split("/").length - b.rel.split("/").length || a.rel.localeCompare(b.rel)
13220
13323
  );
13221
13324
  for (const manifest of byDepth) {
13222
- const base = path38.posix.basename(manifest.rel);
13325
+ const base = path39.posix.basename(manifest.rel);
13223
13326
  if (!/^\.prettierrc/.test(base) && base !== "package.json") continue;
13224
13327
  try {
13225
13328
  const parsed = JSON.parse(manifest.text);
@@ -13237,7 +13340,7 @@ function detectFormatting(manifests, componentFiles) {
13237
13340
  }
13238
13341
  }
13239
13342
  for (const manifest of byDepth) {
13240
- if (path38.posix.basename(manifest.rel) !== ".editorconfig") continue;
13343
+ if (path39.posix.basename(manifest.rel) !== ".editorconfig") continue;
13241
13344
  const style = /indent_style\s*=\s*(tab|space)/.exec(manifest.text)?.[1];
13242
13345
  const width = /indent_size\s*=\s*(\d+)/.exec(manifest.text)?.[1];
13243
13346
  if (style || width) {
@@ -13308,12 +13411,12 @@ function buildDisclosures(detected, css, cappedOut, unrepresentativeClassNames)
13308
13411
  return out;
13309
13412
  }
13310
13413
  function outPathIsGitIgnored(outPath) {
13311
- const dir = path38.dirname(outPath);
13414
+ const dir = path39.dirname(outPath);
13312
13415
  try {
13313
- const ignoreFile = path38.join(path38.dirname(dir), ".gitignore");
13416
+ const ignoreFile = path39.join(path39.dirname(dir), ".gitignore");
13314
13417
  if (!fs3.existsSync(ignoreFile)) return false;
13315
13418
  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);
13419
+ const base = path39.basename(dir);
13317
13420
  return patterns.some((p) => p === base || p === `${base}/` || p === `/${base}` || p === `/${base}/`);
13318
13421
  } catch {
13319
13422
  return false;
@@ -13460,8 +13563,8 @@ __export(profile_exports, {
13460
13563
  PROFILE_DESCRIPTION: () => PROFILE_DESCRIPTION,
13461
13564
  runProfile: () => runProfile
13462
13565
  });
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";
13566
+ import { closeSync, constants, existsSync as existsSync31, mkdirSync as mkdirSync9, openSync, realpathSync as realpathSync4, writeFileSync as writeFileSync13 } from "node:fs";
13567
+ import path40 from "node:path";
13465
13568
  function escapesScanRoot(outPath, scanRoot) {
13466
13569
  const resolveExisting = (target) => {
13467
13570
  let cursor = target;
@@ -13469,23 +13572,23 @@ function escapesScanRoot(outPath, scanRoot) {
13469
13572
  try {
13470
13573
  return realpathSync4(cursor);
13471
13574
  } catch {
13472
- const parent = path39.dirname(cursor);
13575
+ const parent = path40.dirname(cursor);
13473
13576
  if (parent === cursor) return cursor;
13474
13577
  cursor = parent;
13475
13578
  }
13476
13579
  }
13477
13580
  };
13478
13581
  const root = resolveExisting(scanRoot);
13479
- const dir = resolveExisting(path39.dirname(outPath));
13480
- return dir !== root && !dir.startsWith(root + path39.sep);
13582
+ const dir = resolveExisting(path40.dirname(outPath));
13583
+ return dir !== root && !dir.startsWith(root + path40.sep);
13481
13584
  }
13482
13585
  function runProfile(options) {
13483
13586
  if (options.describe) {
13484
13587
  printDescription(PROFILE_DESCRIPTION);
13485
13588
  return;
13486
13589
  }
13487
- const dir = path39.resolve(options.dir ?? ".");
13488
- if (!existsSync30(dir)) {
13590
+ const dir = path40.resolve(options.dir ?? ".");
13591
+ if (!existsSync31(dir)) {
13489
13592
  fail(options, ExitCode.InputValidation, {
13490
13593
  error: `no such directory: ${dir}`,
13491
13594
  code: "profile_dir_missing",
@@ -13493,7 +13596,7 @@ function runProfile(options) {
13493
13596
  });
13494
13597
  }
13495
13598
  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"));
13599
+ const outPath = path40.resolve(options.out ?? path40.join(dir, "tendril-out", "codebase-profile.json"));
13497
13600
  if (!options.dryRun) {
13498
13601
  if (options.out === void 0 && escapesScanRoot(outPath, dir)) {
13499
13602
  fail(options, ExitCode.InputValidation, {
@@ -13502,10 +13605,10 @@ function runProfile(options) {
13502
13605
  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
13606
  });
13504
13607
  }
13505
- mkdirSync9(path39.dirname(outPath), { recursive: true });
13608
+ mkdirSync9(path40.dirname(outPath), { recursive: true });
13506
13609
  const handle = openSync(outPath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 420);
13507
13610
  try {
13508
- writeFileSync12(handle, `${JSON.stringify(profile, null, 2)}
13611
+ writeFileSync13(handle, `${JSON.stringify(profile, null, 2)}
13509
13612
  `, "utf8");
13510
13613
  } finally {
13511
13614
  closeSync(handle);
@@ -13567,7 +13670,7 @@ Written to ${outPath}
13567
13670
  `);
13568
13671
  if (!ignored) {
13569
13672
  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.
13673
+ ` 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
13674
  `
13572
13675
  );
13573
13676
  }
@@ -13699,18 +13802,98 @@ var init_activate = __esm({
13699
13802
  }
13700
13803
  });
13701
13804
 
13805
+ // packages/cli/src/run-presence.ts
13806
+ import { createHash as createHash8 } from "node:crypto";
13807
+ import { existsSync as existsSync32, mkdirSync as mkdirSync10, readFileSync as readFileSync29, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
13808
+ import path41 from "node:path";
13809
+ function presenceDir() {
13810
+ return path41.join(path41.dirname(sessionPath()), "runs");
13811
+ }
13812
+ function presenceFile(componentName) {
13813
+ return path41.join(presenceDir(), `${createHash8("sha256").update(componentName).digest("hex").slice(0, 16)}.json`);
13814
+ }
13815
+ function readCached(componentName) {
13816
+ const file = presenceFile(componentName);
13817
+ if (!existsSync32(file)) return void 0;
13818
+ try {
13819
+ const parsed = JSON.parse(readFileSync29(file, "utf8"));
13820
+ return typeof parsed.runId === "string" && typeof parsed.origin === "string" ? parsed : void 0;
13821
+ } catch {
13822
+ return void 0;
13823
+ }
13824
+ }
13825
+ async function post(origin, token, pathname, body, method = "POST") {
13826
+ const response = await fetch(`${origin}${pathname}`, {
13827
+ method,
13828
+ headers: { authorization: `Bearer ${token}`, ...body === void 0 ? {} : { "content-type": "application/json" } },
13829
+ ...body === void 0 ? {} : { body: JSON.stringify(body) },
13830
+ signal: AbortSignal.timeout(PRESENCE_TIMEOUT_MS)
13831
+ });
13832
+ if (!response.ok) throw new Error(String(response.status));
13833
+ return response.json();
13834
+ }
13835
+ async function reportRunPresence(componentName, phase) {
13836
+ try {
13837
+ const session = readStoredSession();
13838
+ if (session === void 0) return;
13839
+ const cached2 = readCached(componentName);
13840
+ if (cached2 !== void 0 && cached2.origin === session.origin) {
13841
+ const beat = await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, { phase });
13842
+ if (beat.alive === true) return;
13843
+ }
13844
+ const started = await post(session.origin, session.token, "/api/runs", { componentName, phase });
13845
+ if (typeof started.runId !== "string") return;
13846
+ mkdirSync10(presenceDir(), { recursive: true });
13847
+ writeFileSync14(presenceFile(componentName), `${JSON.stringify({ runId: started.runId, origin: session.origin })}
13848
+ `, { mode: 384 });
13849
+ } catch {
13850
+ }
13851
+ }
13852
+ async function endRunPresence(componentName) {
13853
+ try {
13854
+ const session = readStoredSession();
13855
+ const cached2 = readCached(componentName);
13856
+ rmSync5(presenceFile(componentName), { force: true });
13857
+ if (session === void 0 || cached2 === void 0 || cached2.origin !== session.origin) return;
13858
+ await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, void 0, "DELETE");
13859
+ } catch {
13860
+ }
13861
+ }
13862
+ var PRESENCE_TIMEOUT_MS;
13863
+ var init_run_presence = __esm({
13864
+ "packages/cli/src/run-presence.ts"() {
13865
+ "use strict";
13866
+ init_publish_client();
13867
+ PRESENCE_TIMEOUT_MS = 1500;
13868
+ }
13869
+ });
13870
+
13702
13871
  // packages/cli/src/commands/compose.ts
13703
13872
  var compose_exports = {};
13704
13873
  __export(compose_exports, {
13705
13874
  COMPOSE_DESCRIPTION: () => COMPOSE_DESCRIPTION,
13875
+ IMPLICIT_PARENT_SCAN_MAX_ENTRIES: () => IMPLICIT_PARENT_SCAN_MAX_ENTRIES,
13706
13876
  compositionPairsFor: () => compositionPairsFor,
13707
13877
  runCompose: () => runCompose
13708
13878
  });
13709
- import { createHash as createHash8 } from "node:crypto";
13710
- import { existsSync as existsSync31, readFileSync as readFileSync28 } from "node:fs";
13711
- import path40 from "node:path";
13879
+ import { createHash as createHash9 } from "node:crypto";
13880
+ import { existsSync as existsSync33, readFileSync as readFileSync30, readdirSync as readdirSync14 } from "node:fs";
13881
+ import path42 from "node:path";
13712
13882
  function compositionPairsFor(hostSet, roots) {
13713
- const scanRoots = [.../* @__PURE__ */ new Set([...roots, path40.dirname(hostSet)])];
13883
+ const parent = path42.dirname(hostSet);
13884
+ const explicitRoots = [...new Set(roots)];
13885
+ let skippedParent;
13886
+ let parentRoot = [];
13887
+ if (!explicitRoots.some((r) => path42.resolve(r) === path42.resolve(parent))) {
13888
+ let parentEntries = 0;
13889
+ try {
13890
+ parentEntries = readdirSync14(parent).length;
13891
+ } catch {
13892
+ }
13893
+ if (parentEntries > IMPLICIT_PARENT_SCAN_MAX_ENTRIES) skippedParent = { dir: parent, entries: parentEntries };
13894
+ else parentRoot = [parent];
13895
+ }
13896
+ const scanRoots = [.../* @__PURE__ */ new Set([...explicitRoots, ...parentRoot])];
13714
13897
  const edges = composeReport(buildComposeIndex(scanRoots));
13715
13898
  const pairs = substitutionPairs(edges, hostSet);
13716
13899
  const { raw } = readManifestFile(hostSet);
@@ -13723,7 +13906,7 @@ function compositionPairsFor(hostSet, roots) {
13723
13906
  else invalid++;
13724
13907
  }
13725
13908
  const decidedKeys = new Set(standing.map((c) => fromStoredRel(c.partner.key)));
13726
- return { open: pairs.filter((p) => !decidedKeys.has(p.key)), standing, invalid };
13909
+ return { open: pairs.filter((p) => !decidedKeys.has(p.key)), standing, invalid, ...skippedParent !== void 0 ? { skippedParent } : {} };
13727
13910
  }
13728
13911
  function substitutionPairs(edges, hostSet) {
13729
13912
  const pairs = /* @__PURE__ */ new Map();
@@ -13742,7 +13925,7 @@ function substitutionPairs(edges, hostSet) {
13742
13925
  });
13743
13926
  }
13744
13927
  const pair = pairs.get(key);
13745
- const poseDisplay = e.pose.reps.map((r) => `${path40.basename(r.dir)}:${r.slug}`).join(", ");
13928
+ const poseDisplay = e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ");
13746
13929
  pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
13747
13930
  for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
13748
13931
  }
@@ -13754,7 +13937,7 @@ function runCompose(flags) {
13754
13937
  return;
13755
13938
  }
13756
13939
  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];
13940
+ const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path42.resolve(base, d)) : [base];
13758
13941
  if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
13759
13942
  fail(flags, ExitCode.InputValidation, {
13760
13943
  error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
@@ -13763,7 +13946,7 @@ function runCompose(flags) {
13763
13946
  });
13764
13947
  }
13765
13948
  if (flags.set !== void 0) {
13766
- runComposeConfirm(flags, path40.resolve(base, flags.set), roots);
13949
+ runComposeConfirm(flags, path42.resolve(base, flags.set), roots);
13767
13950
  return;
13768
13951
  }
13769
13952
  const index = buildComposeIndex(roots);
@@ -13781,7 +13964,7 @@ function runCompose(flags) {
13781
13964
  }
13782
13965
  let lastHost = "";
13783
13966
  for (const e of edges) {
13784
- const host = `${path40.basename(e.hostSet)}`;
13967
+ const host = `${path42.basename(e.hostSet)}`;
13785
13968
  if (host !== lastHost) {
13786
13969
  process.stdout.write(`
13787
13970
  ${host}
@@ -13789,7 +13972,7 @@ ${host}
13789
13972
  lastHost = host;
13790
13973
  }
13791
13974
  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(", ")})` : "";
13975
+ const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
13793
13976
  process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
13794
13977
  `);
13795
13978
  for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
@@ -13801,14 +13984,14 @@ ${NOTE}
13801
13984
  });
13802
13985
  }
13803
13986
  function runComposeConfirm(flags, hostSet, roots) {
13804
- if (!existsSync31(path40.join(hostSet, "recording-set.json"))) {
13987
+ if (!existsSync33(path42.join(hostSet, "recording-set.json"))) {
13805
13988
  fail(flags, ExitCode.InputValidation, {
13806
13989
  error: `no recording-set.json in ${hostSet}`,
13807
13990
  code: "no-recording-set",
13808
13991
  remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
13809
13992
  });
13810
13993
  }
13811
- const scanRoots = [.../* @__PURE__ */ new Set([...roots, path40.dirname(hostSet)])];
13994
+ const scanRoots = [.../* @__PURE__ */ new Set([...roots, path42.dirname(hostSet)])];
13812
13995
  const index = buildComposeIndex(scanRoots);
13813
13996
  const edges = composeReport(index);
13814
13997
  const pairs = substitutionPairs(edges, hostSet);
@@ -13891,7 +14074,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
13891
14074
  // full recording-set hash join lands with pin authoring, where
13892
14075
  // task configs exist.)
13893
14076
  manifestSha256: Object.fromEntries(
13894
- p.partnerDirs.map((d) => [path40.relative(hostSet, d), createHash8("sha256").update(readFileSync28(path40.join(d, "recording-set.json"))).digest("hex")])
14077
+ p.partnerDirs.map((d) => [path42.relative(hostSet, d), createHash9("sha256").update(readFileSync30(path42.join(d, "recording-set.json"))).digest("hex")])
13895
14078
  )
13896
14079
  },
13897
14080
  // Schema shape ONLY (review: the display-side poseDisplay field
@@ -13915,7 +14098,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
13915
14098
  `);
13916
14099
  });
13917
14100
  }
13918
- var COMPOSE_DESCRIPTION, NOTE;
14101
+ var COMPOSE_DESCRIPTION, NOTE, IMPLICIT_PARENT_SCAN_MAX_ENTRIES;
13919
14102
  var init_compose2 = __esm({
13920
14103
  "packages/cli/src/commands/compose.ts"() {
13921
14104
  "use strict";
@@ -13947,6 +14130,7 @@ var init_compose2 = __esm({
13947
14130
  examples: ["tendril compose --list", "tendril compose --list --library ./recordings --json", "tendril compose --set ./recordings/dialog --confirm-compositions"]
13948
14131
  };
13949
14132
  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.";
14133
+ IMPLICIT_PARENT_SCAN_MAX_ENTRIES = 64;
13950
14134
  }
13951
14135
  });
13952
14136
 
@@ -13970,10 +14154,10 @@ __export(record_exports, {
13970
14154
  runRecordPlan: () => runRecordPlan,
13971
14155
  runRecordStatus: () => runRecordStatus
13972
14156
  });
13973
- import { existsSync as existsSync32, mkdtempSync as mkdtempSync2, readFileSync as readFileSync29, readdirSync as readdirSync14 } from "node:fs";
14157
+ import { existsSync as existsSync34, mkdtempSync as mkdtempSync2, readFileSync as readFileSync31, readdirSync as readdirSync15 } from "node:fs";
13974
14158
  import os7 from "node:os";
13975
- import path41 from "node:path";
13976
- import { writeFileSync as writeFileSync13 } from "node:fs";
14159
+ import path43 from "node:path";
14160
+ import { writeFileSync as writeFileSync15 } from "node:fs";
13977
14161
  function recordsInteractionState(reports) {
13978
14162
  const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
13979
14163
  return reports.some((r) => evident(r.axis) || r.domain.some(evident));
@@ -13995,7 +14179,7 @@ function interactionDisclosure(component, reports) {
13995
14179
  };
13996
14180
  }
13997
14181
  function symbolsFromMetadataEnvelope(file, sourceFrame) {
13998
- const env = JSON.parse(readFileSync29(file, "utf8"));
14182
+ const env = JSON.parse(readFileSync31(file, "utf8"));
13999
14183
  const text = env.content.map((c) => c.text ?? "").join("\n");
14000
14184
  const symbols = [];
14001
14185
  const walk2 = (node, ancestor) => {
@@ -14053,8 +14237,8 @@ function runRecordPlan(opts) {
14053
14237
  if (rawFile !== void 0) {
14054
14238
  try {
14055
14239
  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));
14240
+ const tmp = path43.join(mkdtempSync2(path43.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
14241
+ writeFileSync15(tmp, JSON.stringify(envelope));
14058
14242
  metadataEntries.push({ file: tmp });
14059
14243
  } catch (err) {
14060
14244
  fail(opts, ExitCode.InputValidation, {
@@ -14075,7 +14259,7 @@ function runRecordPlan(opts) {
14075
14259
  let metadataTruncated = false;
14076
14260
  for (const { file, frame } of metadataEntries) {
14077
14261
  try {
14078
- const parsed = symbolsFromMetadataEnvelope(path41.resolve(file), frame);
14262
+ const parsed = symbolsFromMetadataEnvelope(path43.resolve(file), frame);
14079
14263
  symbols.push(...parsed.symbols);
14080
14264
  if (parsed.truncated) metadataTruncated = true;
14081
14265
  } catch (err) {
@@ -14109,7 +14293,7 @@ function runRecordPlan(opts) {
14109
14293
  if (symbols.length === 0) {
14110
14294
  const leads = metadataEntries.flatMap(({ file }) => {
14111
14295
  try {
14112
- const env = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
14296
+ const env = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14113
14297
  return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
14114
14298
  } catch {
14115
14299
  return [];
@@ -14202,7 +14386,7 @@ function runRecordPlan(opts) {
14202
14386
  // a CLI flag and the MCP surface has no parameter for it.
14203
14387
  decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
14204
14388
  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`]
14389
+ userRuns: [`rm ${quoteArg(path43.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
14206
14390
  },
14207
14391
  {
14208
14392
  id: "larger-allowance",
@@ -14212,6 +14396,7 @@ function runRecordPlan(opts) {
14212
14396
  ],
14213
14397
  limitHintIfWhoamiIsSilent: LIMIT_HINT
14214
14398
  };
14399
+ void reportRunPresence(opts.component, "recording");
14215
14400
  emitData(
14216
14401
  opts,
14217
14402
  {
@@ -14400,7 +14585,7 @@ function runRecordNext(opts) {
14400
14585
  const progress = payload["progress"];
14401
14586
  process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
14402
14587
  \u2192 ${payload["note"]}
14403
- \u2192 then: ${tendrilCommand(`record ingest --set ${path41.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
14588
+ \u2192 then: ${tendrilCommand(`record ingest --set ${path43.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
14404
14589
  `);
14405
14590
  });
14406
14591
  }
@@ -14474,7 +14659,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14474
14659
  const skipped = [];
14475
14660
  const failed = [];
14476
14661
  for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
14477
- if (existsSync32(path41.join(setDir, rep, name))) {
14662
+ if (existsSync34(path43.join(setDir, rep, name))) {
14478
14663
  skipped.push(name);
14479
14664
  continue;
14480
14665
  }
@@ -14496,16 +14681,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14496
14681
  }
14497
14682
  function rawEnvelopeFromFile(file, parts) {
14498
14683
  if (parts) {
14499
- const blocks = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
14684
+ const blocks = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14500
14685
  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
14686
  return { content: blocks.map((text) => ({ type: "text", text })) };
14502
14687
  }
14503
- return { content: [{ type: "text", text: readFileSync29(path41.resolve(file), "utf8") }] };
14688
+ return { content: [{ type: "text", text: readFileSync31(path43.resolve(file), "utf8") }] };
14504
14689
  }
14505
14690
  async function runRecordIngest(opts) {
14506
14691
  let payload;
14507
14692
  try {
14508
- payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync29(path41.resolve(opts.file), "utf8"));
14693
+ payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync31(path43.resolve(opts.file), "utf8"));
14509
14694
  } catch (err) {
14510
14695
  fail(opts, ExitCode.InputValidation, {
14511
14696
  error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
@@ -14517,7 +14702,7 @@ async function runRecordIngest(opts) {
14517
14702
  fail(opts, ExitCode.InputValidation, {
14518
14703
  error: "--raw is for text tool responses; screenshots are binary",
14519
14704
  code: "envelope-invalid",
14520
- remediation: `Use \`${tendrilCommand(`record fetch --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14705
+ remediation: `Use \`${tendrilCommand(`record fetch --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14521
14706
  });
14522
14707
  }
14523
14708
  if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
@@ -14537,7 +14722,7 @@ async function runRecordIngest(opts) {
14537
14722
  remediation: REINGEST_GUIDANCE
14538
14723
  });
14539
14724
  }
14540
- writeFileSync13(path41.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14725
+ writeFileSync15(path43.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14541
14726
  `);
14542
14727
  emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14543
14728
  process.stdout.write("set-level get_variable_defs ingested\n");
@@ -14561,7 +14746,7 @@ async function runRecordIngest(opts) {
14561
14746
  remediation: REINGEST_GUIDANCE
14562
14747
  });
14563
14748
  }
14564
- writeFileSync13(path41.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14749
+ writeFileSync15(path43.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14565
14750
  `);
14566
14751
  emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14567
14752
  process.stdout.write("set-level get_motion_context ingested\n");
@@ -14577,7 +14762,7 @@ async function runRecordIngest(opts) {
14577
14762
  if (assets !== void 0) {
14578
14763
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14579
14764
  `);
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>`)}
14765
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14581
14766
  `);
14582
14767
  }
14583
14768
  });
@@ -14650,15 +14835,15 @@ async function runRecordIngestRep(opts) {
14650
14835
  if (assets !== void 0) {
14651
14836
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14652
14837
  `);
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>`)}
14838
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14654
14839
  `);
14655
14840
  }
14656
14841
  });
14657
14842
  }
14658
14843
  function runRecordAsset(opts) {
14659
14844
  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));
14845
+ const dir = path43.resolve(opts.dir);
14846
+ const names = readdirSync15(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
14662
14847
  if (names.length === 0) {
14663
14848
  fail(opts, ExitCode.InputValidation, {
14664
14849
  error: `no asset-*.<ext> files found in ${dir}`,
@@ -14669,7 +14854,7 @@ function runRecordAsset(opts) {
14669
14854
  const ingested = [];
14670
14855
  try {
14671
14856
  for (const name of names) {
14672
- ingestAsset(opts.setDir, opts.rep, name, readFileSync29(path41.join(dir, name)));
14857
+ ingestAsset(opts.setDir, opts.rep, name, readFileSync31(path43.join(dir, name)));
14673
14858
  ingested.push(name);
14674
14859
  }
14675
14860
  } catch (err) {
@@ -14689,11 +14874,11 @@ function runRecordAsset(opts) {
14689
14874
  fail(opts, ExitCode.InputValidation, {
14690
14875
  error: "pass --name and --file for a single asset, or --dir for a batch",
14691
14876
  code: "asset-rejected",
14692
- remediation: tendrilCommand(`record asset --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14877
+ remediation: tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14693
14878
  });
14694
14879
  }
14695
14880
  try {
14696
- ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync29(path41.resolve(opts.file)));
14881
+ ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync31(path43.resolve(opts.file)));
14697
14882
  emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
14698
14883
  process.stdout.write(`ingested ${opts.rep}/${opts.name}
14699
14884
  `);
@@ -14710,8 +14895,8 @@ function runRecordStatus(opts) {
14710
14895
  const status = sessionStatus(opts.setDir);
14711
14896
  const composition = (() => {
14712
14897
  try {
14713
- const setDir = path41.resolve(opts.setDir);
14714
- const { open, standing, invalid } = compositionPairsFor(setDir, [path41.dirname(setDir)]);
14898
+ const setDir = path43.resolve(opts.setDir);
14899
+ const { open, standing, invalid } = compositionPairsFor(setDir, [path43.dirname(setDir)]);
14715
14900
  return {
14716
14901
  openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
14717
14902
  confirmed: standing.filter((s) => s.status === "confirmed").length,
@@ -14732,7 +14917,7 @@ function runRecordStatus(opts) {
14732
14917
  }
14733
14918
  }
14734
14919
  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\`
14920
+ status.motion.recorded ? motionTruthFor(path43.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
14921
  ` : "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
14922
  );
14738
14923
  if ("unavailable" in composition) {
@@ -14740,7 +14925,7 @@ function runRecordStatus(opts) {
14740
14925
  `);
14741
14926
  } else if (composition.openPairs.length > 0) {
14742
14927
  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.
14928
+ `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 ${path43.resolve(opts.setDir)}`)}\` lists it; confirmation is human-only.
14744
14929
  `
14745
14930
  );
14746
14931
  } else if (composition.confirmed > 0) {
@@ -14780,7 +14965,7 @@ function narrowedRoles(derived, override) {
14780
14965
  function rolesFromFile(opts, file, derived) {
14781
14966
  let json;
14782
14967
  try {
14783
- json = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
14968
+ json = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14784
14969
  } catch (err) {
14785
14970
  fail(opts, ExitCode.InputValidation, {
14786
14971
  error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -14818,11 +15003,11 @@ function rolesFromFile(opts, file, derived) {
14818
15003
  };
14819
15004
  }
14820
15005
  function runRecordFinish(opts) {
14821
- if (!existsSync32(path41.join(opts.setDir, "recording-set.json"))) {
15006
+ if (!existsSync34(path43.join(opts.setDir, "recording-set.json"))) {
14822
15007
  fail(opts, ExitCode.InputValidation, {
14823
15008
  error: `no recording-set.json in ${opts.setDir}`,
14824
15009
  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.`
15010
+ remediation: `Run \`${tendrilCommand(`record plan --set ${path43.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
14826
15011
  });
14827
15012
  }
14828
15013
  const { manifest, raw } = readManifestFile(opts.setDir);
@@ -14850,17 +15035,17 @@ function runRecordFinish(opts) {
14850
15035
  fail(opts, ExitCode.ConfirmationRequired, {
14851
15036
  error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
14852
15037
  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.`
15038
+ remediation: `A human runs \`${tendrilCommand(`record finish --set ${path43.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
14854
15039
  });
14855
15040
  }
14856
15041
  const merged = { ...raw, roles };
14857
- const { issues } = validateRecordingSet(merged, (rel) => existsSync32(path41.join(opts.setDir, rel)));
15042
+ const { issues } = validateRecordingSet(merged, (rel) => existsSync34(path43.join(opts.setDir, rel)));
14858
15043
  const errors = issues.filter((i) => i.severity === "error");
14859
15044
  if (errors.length > 0) {
14860
15045
  fail(opts, ExitCode.InputValidation, {
14861
15046
  error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
14862
15047
  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\`.`
15048
+ remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path43.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
14864
15049
  });
14865
15050
  }
14866
15051
  for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
@@ -14879,6 +15064,7 @@ var init_record = __esm({
14879
15064
  init_src();
14880
15065
  init_src4();
14881
15066
  init_output();
15067
+ init_run_presence();
14882
15068
  init_entitlement();
14883
15069
  init_invocation();
14884
15070
  init_compose2();
@@ -14910,9 +15096,9 @@ var init_record = __esm({
14910
15096
  });
14911
15097
 
14912
15098
  // packages/cli/src/font-guidance.ts
14913
- import path42 from "node:path";
15099
+ import path44 from "node:path";
14914
15100
  function fontsUnprovenRemediation(setDir) {
14915
- const set = setDir === void 0 ? void 0 : path42.resolve(setDir);
15101
+ const set = setDir === void 0 ? void 0 : path44.resolve(setDir);
14916
15102
  if (set !== void 0) {
14917
15103
  try {
14918
15104
  const needs = recordedFontNeeds(set);
@@ -14987,8 +15173,8 @@ __export(fonts_exports, {
14987
15173
  runFontsResolveSet: () => runFontsResolveSet,
14988
15174
  runFontsStatus: () => runFontsStatus
14989
15175
  });
14990
- import { existsSync as existsSync33, readFileSync as readFileSync30 } from "node:fs";
14991
- import path43 from "node:path";
15176
+ import { existsSync as existsSync35, readFileSync as readFileSync32 } from "node:fs";
15177
+ import path45 from "node:path";
14992
15178
  async function runFontsResolve(opts) {
14993
15179
  const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
14994
15180
  const queryRefusal = systemFaceRefusal(opts.family.trim());
@@ -15009,7 +15195,7 @@ async function runFontsResolve(opts) {
15009
15195
  }
15010
15196
  }
15011
15197
  async function runFontsResolveSet(opts) {
15012
- const setDir = path43.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
15198
+ const setDir = path45.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
15013
15199
  let needs = [];
15014
15200
  try {
15015
15201
  needs = recordedFontNeeds(setDir);
@@ -15104,16 +15290,16 @@ async function runFontsResolveSet(opts) {
15104
15290
  }
15105
15291
  }
15106
15292
  function runFontsStatus(opts) {
15107
- const manifestPath2 = path43.join(opts.cacheDir, "manifest.json");
15108
- if (!existsSync33(manifestPath2)) {
15293
+ const manifestPath2 = path45.join(opts.cacheDir, "manifest.json");
15294
+ if (!existsSync35(manifestPath2)) {
15109
15295
  fail(opts, ExitCode.FontsUnproven, {
15110
15296
  error: `no font cache at ${opts.cacheDir}`,
15111
15297
  code: "fonts-unresolved",
15112
15298
  remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
15113
15299
  });
15114
15300
  }
15115
- const faces = JSON.parse(readFileSync30(manifestPath2, "utf8"));
15116
- const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path43.resolve(opts.lock), opts.cacheDir) : null;
15301
+ const faces = JSON.parse(readFileSync32(manifestPath2, "utf8"));
15302
+ const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path45.resolve(opts.lock), opts.cacheDir) : null;
15117
15303
  emitData(opts, { faces, lockVerdicts }, () => {
15118
15304
  for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
15119
15305
  `);
@@ -15157,13 +15343,13 @@ function familyMismatch(family, declared) {
15157
15343
  }
15158
15344
  function runFontsAdd(opts) {
15159
15345
  if (opts.set !== void 0) {
15160
- const declared = taskFontFamilies(path43.resolve(opts.set)) ?? [];
15346
+ const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
15161
15347
  const mismatch = familyMismatch(opts.family, declared);
15162
15348
  if (mismatch !== void 0) {
15163
15349
  fail(opts, ExitCode.InputValidation, {
15164
15350
  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
15351
  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.`
15352
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path45.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
15353
  });
15168
15354
  }
15169
15355
  } else {
@@ -15222,13 +15408,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
15222
15408
  }
15223
15409
  function runFontsAddSystem(opts) {
15224
15410
  if (opts.set !== void 0) {
15225
- const declared = taskFontFamilies(path43.resolve(opts.set)) ?? [];
15411
+ const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
15226
15412
  const mismatch = familyMismatch(opts.family, declared);
15227
15413
  if (mismatch !== void 0) {
15228
15414
  fail(opts, ExitCode.InputValidation, {
15229
15415
  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
15416
  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.`
15417
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path45.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
15232
15418
  });
15233
15419
  }
15234
15420
  } else {
@@ -15278,12 +15464,12 @@ var init_fonts = __esm({
15278
15464
  });
15279
15465
 
15280
15466
  // packages/cli/src/profile-input.ts
15281
- import { existsSync as existsSync34, readFileSync as readFileSync31 } from "node:fs";
15282
- import path44 from "node:path";
15467
+ import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
15468
+ import path46 from "node:path";
15283
15469
  function loadCodebaseProfile(flags, profilePath) {
15284
15470
  if (profilePath === void 0) return null;
15285
- const abs = path44.resolve(profilePath);
15286
- if (!existsSync34(abs)) {
15471
+ const abs = path46.resolve(profilePath);
15472
+ if (!existsSync36(abs)) {
15287
15473
  fail(flags, ExitCode.InputValidation, {
15288
15474
  error: `no profile at ${abs}`,
15289
15475
  code: "profile_missing",
@@ -15291,7 +15477,7 @@ function loadCodebaseProfile(flags, profilePath) {
15291
15477
  });
15292
15478
  }
15293
15479
  try {
15294
- return readCodebaseProfile(readFileSync31(abs, "utf8"));
15480
+ return readCodebaseProfile(readFileSync33(abs, "utf8"));
15295
15481
  } catch (error) {
15296
15482
  fail(flags, ExitCode.InputValidation, {
15297
15483
  error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
@@ -15333,8 +15519,8 @@ __export(verify_exports, {
15333
15519
  runVerify: () => runVerify,
15334
15520
  verdictCaveatsFor: () => verdictCaveatsFor
15335
15521
  });
15336
- import { existsSync as existsSync35, readFileSync as readFileSync32, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
15337
- import path45 from "node:path";
15522
+ import { existsSync as existsSync37, readFileSync as readFileSync34, rmSync as rmSync6, writeFileSync as writeFileSync16 } from "node:fs";
15523
+ import path47 from "node:path";
15338
15524
  function interactionCoverage(behaviors) {
15339
15525
  const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
15340
15526
  const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
@@ -15575,12 +15761,12 @@ function compositionReport(input) {
15575
15761
  function eyeCheck(bundleDir) {
15576
15762
  return {
15577
15763
  command: tendrilCommand(`inspect "${bundleDir}"`),
15578
- sheetPath: path45.join(bundleDir, "verify-evidence", "inspect.html"),
15764
+ sheetPath: path47.join(bundleDir, "verify-evidence", "inspect.html"),
15579
15765
  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
15766
  };
15581
15767
  }
15582
15768
  function evidenceArtifacts(evidenceDir, reps) {
15583
- const named = (name) => existsSync35(path45.join(evidenceDir, name)) ? name : null;
15769
+ const named = (name) => existsSync37(path47.join(evidenceDir, name)) ? name : null;
15584
15770
  return {
15585
15771
  legend: named("diff-legend.txt"),
15586
15772
  configs: reps.map((rep) => {
@@ -15628,7 +15814,7 @@ function taskFromManifest(opts, manifest, setDir) {
15628
15814
  const adapterSlugs = Object.keys(manifest.propAdapter);
15629
15815
  const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
15630
15816
  const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
15631
- const registry = Object.values(TASKS).find((t) => path45.resolve(t.set) === path45.resolve(setDir));
15817
+ const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
15632
15818
  const authored = (() => {
15633
15819
  if (registry !== void 0) return void 0;
15634
15820
  try {
@@ -15682,25 +15868,26 @@ function verdictCaveatsFor(input) {
15682
15868
  ...input.operability !== "verified" ? ["operability-unverified"] : [],
15683
15869
  ...(input.pixelOnlyInteractionPoses ?? 0) > 0 ? ["interaction-poses-unchecked"] : [],
15684
15870
  ...input.compositionUnavailable ? ["composition-not-checked"] : [],
15685
- ...input.fontsSubstituted ? ["fonts-substituted"] : []
15871
+ ...input.fontsSubstituted ? ["fonts-substituted"] : [],
15872
+ ...input.hoverBudgetNondefault === true ? ["hover-budget-nondefault"] : []
15686
15873
  ];
15687
15874
  }
15688
15875
  async function runVerify(opts) {
15689
15876
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
15690
15877
  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)) {
15878
+ const setOverride = opts.set !== void 0 ? path47.resolve(callerCwd, opts.set) : void 0;
15879
+ opts = { ...opts, bundleDir: path47.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
15880
+ if (!existsSync37(opts.bundleDir)) {
15694
15881
  fail(opts, ExitCode.InputValidation, {
15695
15882
  error: `bundle directory not found: ${opts.bundleDir}`,
15696
15883
  code: "bundle-missing",
15697
15884
  remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
15698
15885
  });
15699
15886
  }
15700
- const manifestPath2 = path45.join(opts.bundleDir, "component.json");
15887
+ const manifestPath2 = path47.join(opts.bundleDir, "component.json");
15701
15888
  let manifest;
15702
- if (existsSync35(manifestPath2)) {
15703
- const { manifest: parsed, issues } = readBundleManifest(readFileSync32(manifestPath2, "utf8"));
15889
+ if (existsSync37(manifestPath2)) {
15890
+ const { manifest: parsed, issues } = readBundleManifest(readFileSync34(manifestPath2, "utf8"));
15704
15891
  if (issues.length > 0) {
15705
15892
  fail(opts, ExitCode.InputValidation, {
15706
15893
  error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15731,21 +15918,21 @@ async function runVerify(opts) {
15731
15918
  task = registry;
15732
15919
  } else if (manifest !== void 0) {
15733
15920
  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);
15921
+ if (path47.isAbsolute(p)) return p;
15922
+ const fromRepo = path47.resolve(REPO_ROOT, p);
15923
+ if (existsSync37(fromRepo)) return fromRepo;
15924
+ return path47.resolve(callerCwd, p);
15738
15925
  };
15739
15926
  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))) {
15927
+ if (!existsSync37(path47.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path47.resolve(t.set) === path47.resolve(setDir))) {
15741
15928
  fail(opts, ExitCode.RecordingIncomplete, {
15742
15929
  error: `recording set not found or unmanifested: ${setDir}`,
15743
15930
  code: "recording-set-missing",
15744
15931
  remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
15745
15932
  });
15746
15933
  }
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"))) {
15934
+ const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
15935
+ if (registry !== void 0 && !existsSync37(path47.join(setDir, "recording-set.json"))) {
15749
15936
  const recordedSlugs = registry.configs.map((c) => c.rep);
15750
15937
  const adapterSlugs = Object.keys(manifest.propAdapter);
15751
15938
  unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
@@ -15777,9 +15964,9 @@ async function runVerify(opts) {
15777
15964
  recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
15778
15965
  }
15779
15966
  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)));
15967
+ const p = path47.join(opts.bundleDir, name);
15968
+ if (!existsSync37(p)) continue;
15969
+ const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync34(p)));
15783
15970
  if (issues.length > 0) {
15784
15971
  fail(opts, ExitCode.InputValidation, {
15785
15972
  error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15817,7 +16004,7 @@ async function runVerify(opts) {
15817
16004
  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
16005
  }
15819
16006
  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"))
16007
+ (c) => !existsSync37(path47.join(task.set, c.rep, "get_screenshot.json")) || !existsSync37(path47.join(task.set, c.rep, "get_metadata.json"))
15821
16008
  );
15822
16009
  if (missing.length > 0) {
15823
16010
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -15827,8 +16014,8 @@ async function runVerify(opts) {
15827
16014
  });
15828
16015
  }
15829
16016
  const bar = BARS2[opts.bar];
15830
- const evidenceDir = path45.join(opts.bundleDir, "verify-evidence");
15831
- rmSync5(evidenceDir, { recursive: true, force: true });
16017
+ const evidenceDir = path47.join(opts.bundleDir, "verify-evidence");
16018
+ rmSync6(evidenceDir, { recursive: true, force: true });
15832
16019
  const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
15833
16020
  const quality = await checkBundleQuality(
15834
16021
  opts.bundleDir,
@@ -15845,14 +16032,19 @@ async function runVerify(opts) {
15845
16032
  // ASKED, never "follows every convention".
15846
16033
  loadCodebaseProfile(opts, opts.profile) ?? void 0
15847
16034
  );
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");
16035
+ const bundleCss = ["tokens.css", "styles.css"].map((f) => path47.join(opts.bundleDir, f)).filter((f) => existsSync37(f)).map((f) => readFileSync34(f, "utf8")).join("\n");
15849
16036
  const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
15850
- const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs);
16037
+ const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs, {
16038
+ ...opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {},
16039
+ // Run-27 diagnostics: a mount failure's screenshot lands beside the
16040
+ // rest of the evidence (cleared with it on every fresh run).
16041
+ failureShotDir: evidenceDir
16042
+ });
15851
16043
  const framing = checkAdapterFraming(
15852
16044
  task.configs,
15853
16045
  adapterVocabulary ?? buildAdapterVocabulary({ authorityConfigs: authorityConfigs ?? task.configs })
15854
16046
  );
15855
- const behaviors = [...await checkBehaviors(task, opts.bundleDir), ...parity];
16047
+ const behaviors = [...await checkBehaviors(task, opts.bundleDir, opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {}), ...parity];
15856
16048
  const structural = roles !== void 0 ? await checkStructuralComposition(task, opts.bundleDir, roles) : [];
15857
16049
  const regionsOut = roles !== void 0 ? interiorRegions(task.set, roles) : void 0;
15858
16050
  const crops = regionsOut !== void 0 && "regions" in regionsOut ? await checkCropComposition(task, opts.bundleDir, regionsOut.regions) : void 0;
@@ -15861,10 +16053,10 @@ async function runVerify(opts) {
15861
16053
  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
16054
  }
15863
16055
  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: [] };
16056
+ const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path47.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
15865
16057
  const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
15866
16058
  const renderExpectations = verifyPins.pins.map((pin) => ({
15867
- modulePath: path45.join(composedModuleDir(pin.partnerName), pin.entryComponent),
16059
+ modulePath: path47.join(composedModuleDir(pin.partnerName), pin.entryComponent),
15868
16060
  component: pin.entryComponent,
15869
16061
  stampName: `${pin.pairKey}:${pin.entryComponent}`,
15870
16062
  hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
@@ -15932,7 +16124,11 @@ async function runVerify(opts) {
15932
16124
  // Canonical mount semantics are VERSION-scoped (0.1.22 review: the
15933
16125
  // greyscale flip changed Windows pixels under an identical-looking
15934
16126
  // stamp) — the ruler version is part of the comparability key.
15935
- environment: { ...environmentStamp(taskFontFamilies(task.set)), ruler: cliVersion() },
16127
+ // The hover budget is part of how the behavior numbers were
16128
+ // produced (run 27), so it travels with the environment — always,
16129
+ // not only when overridden, so a reader never has to know the
16130
+ // default to interpret a report.
16131
+ environment: { ...environmentStamp(taskFontFamilies(task.set)), ruler: cliVersion(), hoverBudgetMs: opts.hoverTimeoutMs ?? DEFAULT_HOVER_BUDGET_MS },
15936
16132
  coverage: {
15937
16133
  scoredConfigs: statuses.length,
15938
16134
  certified,
@@ -16006,7 +16202,8 @@ async function runVerify(opts) {
16006
16202
  operability: coverage.operability,
16007
16203
  compositionUnavailable: "unavailable" in compositionBlock,
16008
16204
  fontsSubstituted: substitutedFamilies.length > 0,
16009
- pixelOnlyInteractionPoses: unmappedInteractionEvidence.length
16205
+ pixelOnlyInteractionPoses: unmappedInteractionEvidence.length,
16206
+ hoverBudgetNondefault: opts.hoverTimeoutMs !== void 0 && opts.hoverTimeoutMs !== DEFAULT_HOVER_BUDGET_MS
16010
16207
  }),
16011
16208
  // Machine-readable cause (adversarial review): a cert-bar failure
16012
16209
  // with zero pixel/behavior/composition failures was only
@@ -16025,8 +16222,9 @@ async function runVerify(opts) {
16025
16222
  emitData(opts, report, () => {
16026
16223
  for (const s of statuses) {
16027
16224
  const demoted = "demotedBy" in s && Array.isArray(s.demotedBy) ? ` [demoted: ${s.demotedBy.join("; ")}]` : "";
16225
+ const pixels = tierOf(s, BARS2.cert).toUpperCase();
16028
16226
  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}
16227
+ `${(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
16228
  `
16031
16229
  );
16032
16230
  }
@@ -16151,7 +16349,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16151
16349
  }
16152
16350
  process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
16153
16351
  `);
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")));
16352
+ const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path47.join(opts.bundleDir, f)).filter((f) => existsSync37(f)).map((f) => readFileSync34(f, "utf8")).join("\n")));
16155
16353
  if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
16156
16354
  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
16355
  `);
@@ -16206,7 +16404,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16206
16404
  persistReport(opts, report, evidenceDir);
16207
16405
  }
16208
16406
  function persistReport(opts, report, evidenceDir) {
16209
- if (!existsSync35(evidenceDir)) return;
16407
+ if (!existsSync37(evidenceDir)) return;
16210
16408
  const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
16211
16409
  const withExit = {
16212
16410
  ...report,
@@ -16214,9 +16412,9 @@ function persistReport(opts, report, evidenceDir) {
16214
16412
  ...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
16215
16413
  };
16216
16414
  try {
16217
- writeFileSync14(
16218
- path45.join(evidenceDir, VERIFY_REPORT_FILENAME),
16219
- `${JSON.stringify(verifyReportForTransport(withExit, path45.basename(opts.bundleDir)), null, 2)}
16415
+ writeFileSync16(
16416
+ path47.join(evidenceDir, VERIFY_REPORT_FILENAME),
16417
+ `${JSON.stringify(verifyReportForTransport(withExit, path47.basename(opts.bundleDir)), null, 2)}
16220
16418
  `
16221
16419
  );
16222
16420
  } catch (e) {
@@ -16266,11 +16464,11 @@ __export(engine_exports, {
16266
16464
  runEngineBrief: () => runEngineBrief,
16267
16465
  runEngineScore: () => runEngineScore
16268
16466
  });
16269
- import { appendFileSync, existsSync as existsSync36, mkdirSync as mkdirSync10, readFileSync as readFileSync33, writeFileSync as writeFileSync15 } from "node:fs";
16270
- import path46 from "node:path";
16467
+ import { appendFileSync, existsSync as existsSync38, mkdirSync as mkdirSync11, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "node:fs";
16468
+ import path48 from "node:path";
16271
16469
  function resolveEngineTask(opts, callerCwd) {
16272
- const asPath = path46.resolve(callerCwd, opts.taskOrSet);
16273
- const isSet = existsSync36(path46.join(asPath, "recording-set.json"));
16470
+ const asPath = path48.resolve(callerCwd, opts.taskOrSet);
16471
+ const isSet = existsSync38(path48.join(asPath, "recording-set.json"));
16274
16472
  const registry = TASKS[opts.taskOrSet];
16275
16473
  if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
16276
16474
  if (isSet) {
@@ -16279,7 +16477,7 @@ function resolveEngineTask(opts, callerCwd) {
16279
16477
  for (const d of authored.disclosures) warn(opts, d);
16280
16478
  return {
16281
16479
  task: authored.task,
16282
- name: path46.basename(asPath),
16480
+ name: path48.basename(asPath),
16283
16481
  ref: asPath,
16284
16482
  disclosures: authored.disclosures,
16285
16483
  interactionEvidence: authored.api.interactionEvidence,
@@ -16306,14 +16504,20 @@ function runEngineBrief(opts) {
16306
16504
  requireEntitlement(opts);
16307
16505
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16308
16506
  const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
16507
+ void reportRunPresence(name, "implementing");
16309
16508
  const bar = BARS3[opts.bar];
16310
- if (existsSync36(path46.join(task.set, "recording-set.json"))) {
16509
+ if (existsSync38(path48.join(task.set, "recording-set.json"))) {
16311
16510
  try {
16312
- const { open } = compositionPairsFor(path46.resolve(task.set), [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
16511
+ const { open, skippedParent } = compositionPairsFor(path48.resolve(task.set), [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16512
+ if (skippedParent !== void 0) {
16513
+ disclosures.push(
16514
+ `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.`
16515
+ );
16516
+ }
16313
16517
  if (open.length > 0) {
16314
16518
  const componentNames = [...new Set(open.map((p) => p.displayName))];
16315
16519
  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.`
16520
+ `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 ${path48.resolve(task.set)}`)}\` in their terminal, and re-emit this brief. Proceeding as-is is valid; it just is not composition.`
16317
16521
  );
16318
16522
  }
16319
16523
  } catch (err) {
@@ -16330,9 +16534,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16330
16534
  const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
16331
16535
  const segments = buildSegments(task, "files");
16332
16536
  let notRecorded;
16333
- const manifestPath2 = path46.join(task.set, "recording-set.json");
16334
- if (existsSync36(manifestPath2)) {
16335
- notRecorded = JSON.parse(readFileSync33(manifestPath2, "utf8")).notRecorded;
16537
+ const manifestPath2 = path48.join(task.set, "recording-set.json");
16538
+ if (existsSync38(manifestPath2)) {
16539
+ notRecorded = JSON.parse(readFileSync35(manifestPath2, "utf8")).notRecorded;
16336
16540
  }
16337
16541
  const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
16338
16542
 
@@ -16340,7 +16544,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16340
16544
  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
16545
  ${notRecorded}` : "";
16342
16546
  let fontProvisioning;
16343
- if (existsSync36(manifestPath2)) {
16547
+ if (existsSync38(manifestPath2)) {
16344
16548
  const missingFams = unprovisionedFamilies(task.set);
16345
16549
  const unprovided = unprovisionedFaces(task.set);
16346
16550
  const weightOnly = missingFams.length === 0;
@@ -16362,7 +16566,7 @@ ${notRecorded}` : "";
16362
16566
  };
16363
16567
  }
16364
16568
  }
16365
- const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
16569
+ const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16366
16570
  const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
16367
16571
  const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
16368
16572
 
@@ -16398,10 +16602,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
16398
16602
 
16399
16603
  === TASK PAYLOAD (recorded truth, verbatim) ===
16400
16604
  ${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);
16605
+ const payloadFile = path48.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
16606
+ const candidateDirSuggestion = path48.resolve(callerCwd, `tendril-out/${name}-candidate`);
16607
+ mkdirSync11(path48.dirname(payloadFile), { recursive: true });
16608
+ writeFileSync17(payloadFile, payload);
16405
16609
  emitData(
16406
16610
  opts,
16407
16611
  {
@@ -16447,7 +16651,7 @@ ${segments}`;
16447
16651
  // command must search the same bundle roots the pins came
16448
16652
  // from, or the oracle and the brief describe different worlds.
16449
16653
  `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`
16654
+ `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path48.resolve(callerCwd, opts.library))}` : ""} --json`
16451
16655
  )}\` \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
16656
  "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
16657
  "Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
@@ -16462,8 +16666,8 @@ ${segments}`;
16462
16666
  );
16463
16667
  }
16464
16668
  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;
16669
+ const file = path48.join(candidateDir, "score-history.jsonl");
16670
+ const starts = existsSync38(file) ? readFileSync35(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
16467
16671
  const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
16468
16672
  appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
16469
16673
  `);
@@ -16471,9 +16675,10 @@ function appendScoreHistory(candidateDir, entry) {
16471
16675
  async function runEngineScore(opts) {
16472
16676
  requireEntitlement(opts);
16473
16677
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16474
- const candidateDir = path46.resolve(callerCwd, opts.candidateDir);
16678
+ const candidateDir = path48.resolve(callerCwd, opts.candidateDir);
16475
16679
  const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
16476
- if (!existsSync36(candidateDir)) {
16680
+ void reportRunPresence(name, "implementing");
16681
+ if (!existsSync38(candidateDir)) {
16477
16682
  fail(opts, ExitCode.InputValidation, {
16478
16683
  error: `candidate directory not found: ${candidateDir}`,
16479
16684
  code: "candidate-missing",
@@ -16498,10 +16703,10 @@ async function runEngineScore(opts) {
16498
16703
  for (const g of missingWeights(task.set)) {
16499
16704
  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
16705
  }
16501
- if (opts.rebind !== true && existsSync36(path46.join(candidateDir, "component.json"))) {
16706
+ if (opts.rebind !== true && existsSync38(path48.join(candidateDir, "component.json"))) {
16502
16707
  const prior = (() => {
16503
16708
  try {
16504
- const read = readBundleManifest(readFileSync33(path46.join(candidateDir, "component.json"), "utf8"));
16709
+ const read = readBundleManifest(readFileSync35(path48.join(candidateDir, "component.json"), "utf8"));
16505
16710
  return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
16506
16711
  } catch {
16507
16712
  return { unreadable: true };
@@ -16523,14 +16728,15 @@ async function runEngineScore(opts) {
16523
16728
  }
16524
16729
  }
16525
16730
  const bar = BARS3[opts.bar];
16526
- const evidenceDir = path46.join(candidateDir, "verify-evidence");
16731
+ const evidenceDir = path48.join(candidateDir, "verify-evidence");
16527
16732
  const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
16528
- const parity = await checkHoverParity(task, candidateDir, task.configs);
16733
+ const hoverOpts = opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {};
16734
+ const parity = await checkHoverParity(task, candidateDir, task.configs, { ...hoverOpts, failureShotDir: evidenceDir });
16529
16735
  const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
16530
16736
  const occlusionFailures = occlusionRows.filter((o) => !o.pass);
16531
- const scorePins = composedPins(task.set, [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
16737
+ const scorePins = composedPins(task.set, [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16532
16738
  const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
16533
- const behaviors = [...await checkBehaviors(task, candidateDir), ...parity, ...composition];
16739
+ const behaviors = [...await checkBehaviors(task, candidateDir, hoverOpts), ...parity, ...composition];
16534
16740
  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
16741
  const obj = objective(scores, behaviors);
16536
16742
  const total = scores.length + behaviors.length;
@@ -16733,6 +16939,7 @@ var init_engine2 = __esm({
16733
16939
  init_entitlement();
16734
16940
  init_verify();
16735
16941
  init_compose2();
16942
+ init_run_presence();
16736
16943
  init_src5();
16737
16944
  BARS3 = {
16738
16945
  pass: { sim: 0.95, ink: 0.95 },
@@ -16746,11 +16953,11 @@ var codeconnect_exports = {};
16746
16953
  __export(codeconnect_exports, {
16747
16954
  runCodeConnect: () => runCodeConnect
16748
16955
  });
16749
- import { existsSync as existsSync37, readFileSync as readFileSync34, writeFileSync as writeFileSync16 } from "node:fs";
16750
- import path47 from "node:path";
16956
+ import { existsSync as existsSync39, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
16957
+ import path49 from "node:path";
16751
16958
  function runCodeConnect(opts) {
16752
16959
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16753
- const bundleDir = path47.resolve(callerCwd, opts.bundleDir);
16960
+ const bundleDir = path49.resolve(callerCwd, opts.bundleDir);
16754
16961
  let url;
16755
16962
  try {
16756
16963
  url = new URL(opts.figmaUrl);
@@ -16766,7 +16973,7 @@ function runCodeConnect(opts) {
16766
16973
  }
16767
16974
  let manifest;
16768
16975
  try {
16769
- const read = readBundleManifest(readFileSync34(path47.join(bundleDir, "component.json"), "utf8"));
16976
+ const read = readBundleManifest(readFileSync36(path49.join(bundleDir, "component.json"), "utf8"));
16770
16977
  if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
16771
16978
  manifest = read.manifest;
16772
16979
  } catch (err) {
@@ -16776,8 +16983,8 @@ function runCodeConnect(opts) {
16776
16983
  remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
16777
16984
  });
16778
16985
  }
16779
- const setDir = path47.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16780
- if (!existsSync37(path47.join(setDir, "recording-set.json"))) {
16986
+ const setDir = path49.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16987
+ if (!existsSync39(path49.join(setDir, "recording-set.json"))) {
16781
16988
  fail(opts, ExitCode.InputValidation, {
16782
16989
  error: `recording set not found at ${setDir}`,
16783
16990
  code: "codeconnect-no-set",
@@ -16798,10 +17005,10 @@ function runCodeConnect(opts) {
16798
17005
  const component = api.component;
16799
17006
  const recManifest = loadManifest(setDir);
16800
17007
  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;
17008
+ const meta = path49.join(setDir, r.slug, "get_metadata.json");
17009
+ if (!existsSync39(meta)) return void 0;
16803
17010
  try {
16804
- return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync34(meta, "utf8"))))?.[1];
17011
+ return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync36(meta, "utf8"))))?.[1];
16805
17012
  } catch {
16806
17013
  return void 0;
16807
17014
  }
@@ -16866,7 +17073,7 @@ function runCodeConnect(opts) {
16866
17073
  axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
16867
17074
  fragmentVars.push(varName);
16868
17075
  }
16869
- const entryRel = path47.relative(callerCwd, path47.join(bundleDir, manifest.entry));
17076
+ const entryRel = path49.relative(callerCwd, path49.join(bundleDir, manifest.entry));
16870
17077
  const trust = manifest.trustStatement.split("\n")[0] ?? "";
16871
17078
  const lines = [
16872
17079
  `// url=${opts.figmaUrl}`,
@@ -16890,8 +17097,8 @@ function runCodeConnect(opts) {
16890
17097
  `}`,
16891
17098
  ``
16892
17099
  ].join("\n");
16893
- const outFile = path47.resolve(callerCwd, opts.out ?? path47.join(bundleDir, `${component}.figma.ts`));
16894
- writeFileSync16(outFile, lines);
17100
+ const outFile = path49.resolve(callerCwd, opts.out ?? path49.join(bundleDir, `${component}.figma.ts`));
17101
+ writeFileSync18(outFile, lines);
16895
17102
  emitData(
16896
17103
  opts,
16897
17104
  {
@@ -16929,18 +17136,18 @@ var init_codeconnect = __esm({
16929
17136
  });
16930
17137
 
16931
17138
  // packages/mcp/src/server.ts
16932
- import { createHash as createHash9 } from "node:crypto";
16933
- import { existsSync as existsSync38, mkdtempSync as mkdtempSync3, readFileSync as readFileSync35, readdirSync as readdirSync15, writeFileSync as writeFileSync17 } from "node:fs";
17139
+ import { createHash as createHash10 } from "node:crypto";
17140
+ import { existsSync as existsSync40, mkdtempSync as mkdtempSync3, readFileSync as readFileSync37, readdirSync as readdirSync16, writeFileSync as writeFileSync19 } from "node:fs";
16934
17141
  import os8 from "node:os";
16935
- import path48 from "node:path";
17142
+ import path50 from "node:path";
16936
17143
  import { fileURLToPath as fileURLToPath6 } from "node:url";
16937
17144
  import { z as z14 } from "zod";
16938
17145
  function sourceHash() {
16939
- const dir = path48.dirname(fileURLToPath6(import.meta.url));
16940
- const h = createHash9("sha256");
16941
- for (const f of readdirSync15(dir).filter((n) => n.endsWith(".ts")).sort()) {
17146
+ const dir = path50.dirname(fileURLToPath6(import.meta.url));
17147
+ const h = createHash10("sha256");
17148
+ for (const f of readdirSync16(dir).filter((n) => n.endsWith(".ts")).sort()) {
16942
17149
  h.update(f);
16943
- h.update(readFileSync35(path48.join(dir, f)));
17150
+ h.update(readFileSync37(path50.join(dir, f)));
16944
17151
  }
16945
17152
  return h.digest("hex").slice(0, 16);
16946
17153
  }
@@ -16948,10 +17155,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
16948
17155
  var init_server = __esm({
16949
17156
  "packages/mcp/src/server.ts"() {
16950
17157
  "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] };
17158
+ REPO_ROOT3 = path50.resolve(path50.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
17159
+ CLI_BIN = path50.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
17160
+ BUNDLED_CLI = path50.join(path50.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
17161
+ CLI_SPAWN = existsSync40(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
16955
17162
  str = (d) => z14.string().describe(d);
16956
17163
  optStr = (d) => z14.string().optional().describe(d);
16957
17164
  TOOLS = [
@@ -16982,13 +17189,13 @@ var init_server = __esm({
16982
17189
  const single = i["metadata"];
16983
17190
  const parts = i["metadataParts"];
16984
17191
  if (single !== void 0 || parts !== void 0) {
16985
- const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17192
+ const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
16986
17193
  if (single !== void 0) {
16987
- writeFileSync17(tmp, single);
17194
+ writeFileSync19(tmp, single);
16988
17195
  argvOut.push("--metadata-raw-file", tmp);
16989
17196
  } else {
16990
17197
  if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
16991
- writeFileSync17(tmp, JSON.stringify(parts));
17198
+ writeFileSync19(tmp, JSON.stringify(parts));
16992
17199
  argvOut.push("--metadata-raw-parts-file", tmp);
16993
17200
  }
16994
17201
  }
@@ -17038,6 +17245,24 @@ var init_server = __esm({
17038
17245
  schema: z14.object({}),
17039
17246
  argv: () => ["login", "--device-wait"]
17040
17247
  },
17248
+ {
17249
+ name: "tendril_publish",
17250
+ description: "Publish a VERIFIED bundle to the user's portal \u2014 phase one of the browser-approved publish. Re-publishing an already-published component completes in one call. A component's FIRST publish is a human-only decision the portal enforces: this call requests the approval and returns the approve-page link \u2014 RELAY IT to the user verbatim (they click Approve in the browser where they are signed in; when the page shows a terms checkbox, ticking it is part of their decision \u2014 never advise them to just tick it). Then call tendril_publish_wait to finish. You cannot approve this yourself: the portal only accepts the decision from their signed-in browser, never from this machine's token. Requires a green verify (the CLI refuses a declined run) and a portal session (tendril_login).",
17251
+ schema: z14.object({
17252
+ bundleDir: str("bundle directory (verified \u2014 carries component.json and verify-evidence)"),
17253
+ portal: optStr("portal origin override for self-hosted portals (defaults to the stored session's portal)")
17254
+ }),
17255
+ argv: (i) => ["publish", i["bundleDir"], "--approve-start", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
17256
+ },
17257
+ {
17258
+ name: "tendril_publish_wait",
17259
+ description: "Phase two of the browser-approved publish: waits (with live progress) for the user's Approve click on the page tendril_publish returned, then uploads the bundle, commits it, and returns the live publication URL. Call it right after relaying the approve link. A denial, a lapse, and success each come back as their own sentence \u2014 report the outcome and the URL to the user.",
17260
+ schema: z14.object({
17261
+ bundleDir: str("the same bundle directory tendril_publish was called with"),
17262
+ portal: optStr("portal origin override (must match tendril_publish's)")
17263
+ }),
17264
+ argv: (i) => ["publish", i["bundleDir"], "--approve-wait", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
17265
+ },
17041
17266
  {
17042
17267
  name: "tendril_record_next",
17043
17268
  annotations: { readOnlyHint: true },
@@ -17079,14 +17304,14 @@ var init_server = __esm({
17079
17304
  const bridge = (label, single, parts) => {
17080
17305
  if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
17081
17306
  if (single === void 0 && parts === void 0) return;
17082
- const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17307
+ const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17083
17308
  if (single !== void 0) {
17084
- writeFileSync17(tmp, single);
17309
+ writeFileSync19(tmp, single);
17085
17310
  argvOut.push(`--${label}-file`, tmp);
17086
17311
  } else {
17087
17312
  const blocks = parts;
17088
17313
  if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
17089
- writeFileSync17(tmp, JSON.stringify(blocks));
17314
+ writeFileSync19(tmp, JSON.stringify(blocks));
17090
17315
  argvOut.push(`--${label}-parts-file`, tmp);
17091
17316
  }
17092
17317
  };
@@ -17127,12 +17352,12 @@ var init_server = __esm({
17127
17352
  const file = i["file"];
17128
17353
  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
17354
  if (file !== void 0) return [...base, "--file", file];
17130
- const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17355
+ const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17131
17356
  if (text !== void 0) {
17132
- writeFileSync17(tmp, text);
17357
+ writeFileSync19(tmp, text);
17133
17358
  return [...base, "--file", tmp, "--raw"];
17134
17359
  }
17135
- writeFileSync17(tmp, JSON.stringify(texts));
17360
+ writeFileSync19(tmp, JSON.stringify(texts));
17136
17361
  return [...base, "--file", tmp, "--raw-parts"];
17137
17362
  }
17138
17363
  },
@@ -17289,13 +17514,13 @@ __export(permissions_exports, {
17289
17514
  runPermissions: () => runPermissions,
17290
17515
  writeSelection: () => writeSelection
17291
17516
  });
17292
- import { existsSync as existsSync39, mkdirSync as mkdirSync11, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
17517
+ import { existsSync as existsSync41, mkdirSync as mkdirSync12, readFileSync as readFileSync38, writeFileSync as writeFileSync20 } from "node:fs";
17293
17518
  import os9 from "node:os";
17294
- import path49 from "node:path";
17519
+ import path51 from "node:path";
17295
17520
  function mergeAllowlist(file, entries, denyEntries = []) {
17296
17521
  let settings = {};
17297
- if (existsSync39(file) && readFileSync36(file, "utf8").trim() !== "") {
17298
- settings = JSON.parse(readFileSync36(file, "utf8"));
17522
+ if (existsSync41(file) && readFileSync38(file, "utf8").trim() !== "") {
17523
+ settings = JSON.parse(readFileSync38(file, "utf8"));
17299
17524
  if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
17300
17525
  }
17301
17526
  const permissions = settings["permissions"] ??= {};
@@ -17315,8 +17540,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
17315
17540
  }
17316
17541
  if (added.length > 0 || denyAdded.length > 0) {
17317
17542
  allow.push(...added);
17318
- mkdirSync11(path49.dirname(file), { recursive: true });
17319
- writeFileSync18(file, `${JSON.stringify(settings, null, 2)}
17543
+ mkdirSync12(path51.dirname(file), { recursive: true });
17544
+ writeFileSync20(file, `${JSON.stringify(settings, null, 2)}
17320
17545
  `);
17321
17546
  }
17322
17547
  return { added, alreadyPresent, denyAdded };
@@ -17380,7 +17605,7 @@ async function runPermissions(flags) {
17380
17605
  }
17381
17606
  if (flags.write) {
17382
17607
  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");
17608
+ const file = flags.user ? path51.join(os9.homedir(), ".claude", "settings.json") : path51.join(base, ".claude", "settings.local.json");
17384
17609
  const { entries, denyEntries } = writeSelection(result, flags.user === true);
17385
17610
  if (flags.dryRun) {
17386
17611
  emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
@@ -17529,13 +17754,13 @@ __export(inspect_exports, {
17529
17754
  INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
17530
17755
  runInspect: () => runInspect
17531
17756
  });
17532
- import { existsSync as existsSync40, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
17533
- import path50 from "node:path";
17757
+ import { existsSync as existsSync42, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "node:fs";
17758
+ import path52 from "node:path";
17534
17759
  function readVerifyReport(evidenceDir) {
17535
- const p = path50.join(evidenceDir, VERIFY_REPORT_FILENAME);
17536
- if (!existsSync40(p)) return void 0;
17760
+ const p = path52.join(evidenceDir, VERIFY_REPORT_FILENAME);
17761
+ if (!existsSync42(p)) return void 0;
17537
17762
  try {
17538
- return JSON.parse(readFileSync37(p, "utf8"));
17763
+ return JSON.parse(readFileSync39(p, "utf8"));
17539
17764
  } catch {
17540
17765
  return void 0;
17541
17766
  }
@@ -17563,17 +17788,17 @@ async function runInspect(opts) {
17563
17788
  printDescription(INSPECT_DESCRIPTION);
17564
17789
  return;
17565
17790
  }
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)) {
17791
+ const bundleDir = path52.resolve(opts.bundleDir);
17792
+ const evidenceDir = path52.join(bundleDir, "verify-evidence");
17793
+ const manifestPath2 = path52.join(bundleDir, "component.json");
17794
+ if (!existsSync42(evidenceDir) || !existsSync42(manifestPath2)) {
17570
17795
  fail(opts, ExitCode.InputValidation, {
17571
- error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync40(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
17796
+ error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync42(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
17572
17797
  code: "no-evidence",
17573
17798
  remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
17574
17799
  });
17575
17800
  }
17576
- const { manifest } = readBundleManifest(readFileSync37(manifestPath2, "utf8"));
17801
+ const { manifest } = readBundleManifest(readFileSync39(manifestPath2, "utf8"));
17577
17802
  if (manifest === void 0) {
17578
17803
  fail(opts, ExitCode.InputValidation, {
17579
17804
  error: "component.json did not parse as a bundle manifest",
@@ -17581,9 +17806,9 @@ async function runInspect(opts) {
17581
17806
  remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
17582
17807
  });
17583
17808
  }
17584
- const setDir = path50.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17809
+ const setDir = path52.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17585
17810
  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`)));
17811
+ const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync42(path52.join(evidenceDir, `${rep}-ref.png`)) && existsSync42(path52.join(evidenceDir, `${rep}-render.png`)));
17587
17812
  if (reps.length === 0) {
17588
17813
  fail(opts, ExitCode.InputValidation, {
17589
17814
  error: "verify-evidence holds no ref/render pairs for this bundle's configs",
@@ -17594,15 +17819,15 @@ async function runInspect(opts) {
17594
17819
  let crops = 0;
17595
17820
  const sections = [];
17596
17821
  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`)));
17822
+ const ref = new Uint8Array(readFileSync39(path52.join(evidenceDir, `${rep}-ref.png`)));
17823
+ const render = new Uint8Array(readFileSync39(path52.join(evidenceDir, `${rep}-render.png`)));
17599
17824
  const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
17600
17825
  const cells = [];
17601
17826
  for (const [i, n] of nodes.entries()) {
17602
17827
  const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
17603
17828
  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));
17829
+ writeFileSync21(path52.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
17830
+ writeFileSync21(path52.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
17606
17831
  } catch {
17607
17832
  continue;
17608
17833
  }
@@ -17619,8 +17844,8 @@ async function runInspect(opts) {
17619
17844
  if (reps.includes(c.rep)) continue;
17620
17845
  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
17846
  }
17622
- const sheet = path50.join(evidenceDir, "inspect.html");
17623
- writeFileSync19(
17847
+ const sheet = path52.join(evidenceDir, "inspect.html");
17848
+ writeFileSync21(
17624
17849
  sheet,
17625
17850
  `<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
17626
17851
  body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
@@ -17693,8 +17918,8 @@ __export(login_exports, {
17693
17918
  runLogout: () => runLogout
17694
17919
  });
17695
17920
  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";
17921
+ import { existsSync as existsSync43, mkdirSync as mkdirSync13, readFileSync as readFileSync40, rmSync as rmSync7, writeFileSync as writeFileSync22 } from "node:fs";
17922
+ import path53 from "node:path";
17698
17923
  import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
17699
17924
  async function runLogin(opts, deps) {
17700
17925
  const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
@@ -17788,13 +18013,13 @@ function settleDecision(opts, origin, outcome) {
17788
18013
  }
17789
18014
  }
17790
18015
  function pendingLoginPath() {
17791
- return path51.join(path51.dirname(sessionPath()), "pending-login.json");
18016
+ return path53.join(path53.dirname(sessionPath()), "pending-login.json");
17792
18017
  }
17793
18018
  async function deviceStartPhase(opts, origin, deps) {
17794
18019
  const started = await startHandshake(opts, origin, deps);
17795
18020
  const file = pendingLoginPath();
17796
- mkdirSync12(path51.dirname(file), { recursive: true });
17797
- writeFileSync20(file, `${JSON.stringify({ origin, ...started }, null, 2)}
18021
+ mkdirSync13(path53.dirname(file), { recursive: true });
18022
+ writeFileSync22(file, `${JSON.stringify({ origin, ...started }, null, 2)}
17798
18023
  `, { mode: 384 });
17799
18024
  deps.openBrowser(started.verificationUrl);
17800
18025
  emitData(
@@ -17819,9 +18044,9 @@ async function deviceStartPhase(opts, origin, deps) {
17819
18044
  async function deviceWaitPhase(opts, deps) {
17820
18045
  const file = pendingLoginPath();
17821
18046
  let pending;
17822
- if (existsSync41(file)) {
18047
+ if (existsSync43(file)) {
17823
18048
  try {
17824
- const parsed = JSON.parse(readFileSync38(file, "utf8"));
18049
+ const parsed = JSON.parse(readFileSync40(file, "utf8"));
17825
18050
  if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
17826
18051
  pending = parsed;
17827
18052
  }
@@ -17835,7 +18060,7 @@ async function deviceWaitPhase(opts, deps) {
17835
18060
  remediation: `Start one first: ${tendrilCommand("login --device-start")} (the tendril_login tool).`
17836
18061
  });
17837
18062
  }
17838
- const done = () => rmSync6(file, { force: true });
18063
+ const done = () => rmSync7(file, { force: true });
17839
18064
  const total = Math.ceil(660 / Math.max(1, pending.intervalSeconds));
17840
18065
  let ticks = 0;
17841
18066
  const outcome = await waitForDecision(opts, pending.origin, deps, pending, () => {
@@ -18072,10 +18297,10 @@ var publish_exports = {};
18072
18297
  __export(publish_exports, {
18073
18298
  runPublish: () => runPublish
18074
18299
  });
18075
- import { existsSync as existsSync42, readFileSync as readFileSync39 } from "node:fs";
18076
- import path52 from "node:path";
18300
+ import { existsSync as existsSync44, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync23 } from "node:fs";
18301
+ import path54 from "node:path";
18077
18302
  async function runPublish(opts) {
18078
- const bundleDir = path52.resolve(opts.bundleDir);
18303
+ const bundleDir = path54.resolve(opts.bundleDir);
18079
18304
  const bundle = readBundle(opts, bundleDir);
18080
18305
  const report = bundle.report;
18081
18306
  if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
@@ -18120,7 +18345,7 @@ async function runPublish(opts) {
18120
18345
  const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
18121
18346
  if (sheetEntry !== void 0) {
18122
18347
  const missingCrops = missingInspectCrops(
18123
- readFileSync39(path52.join(bundleDir, sheetEntry.path), "utf8"),
18348
+ readFileSync41(path54.join(bundleDir, sheetEntry.path), "utf8"),
18124
18349
  surface.published.map((p) => p.path)
18125
18350
  );
18126
18351
  if (missingCrops.length > 0) {
@@ -18187,46 +18412,40 @@ async function runPublish(opts) {
18187
18412
  }
18188
18413
  const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
18189
18414
  const client = opts.client ?? httpClient(opts, origin);
18190
- if (opts.confirmPublish === true && process.stdin.isTTY !== true) {
18191
- fail(opts, ExitCode.ConfirmationRequired, {
18192
- error: "--confirm-publish requires an interactive terminal \u2014 putting a component on the internet is a human-only decision",
18193
- code: "publish-confirmation-not-interactive",
18194
- remediation: `A human runs \`${tendrilCommand(`publish ${opts.bundleDir} --confirm-publish`)}\` in their own terminal. Agents: show this to your operator instead of confirming it.`
18195
- });
18196
- }
18197
- if (opts.acceptTerms === true) {
18198
- const accepted = await client.acceptTerms({ figmaFile });
18199
- if (!accepted.ok) refuse2(opts, accepted, "accept-terms-refused");
18415
+ if (opts.approveWait === true) {
18416
+ await approveWaitPhase(opts, client, bundleDir);
18200
18417
  }
18201
- const opened = await client.begin({
18418
+ void reportRunPresence(componentName, "publishing");
18419
+ let opened = await client.begin({
18202
18420
  componentName,
18203
18421
  figmaFile,
18204
18422
  entry: bundle.manifest.entry,
18205
18423
  files: bundle.files,
18206
- report: bundle.reportText,
18207
- confirmed: opts.confirmPublish === true
18424
+ report: bundle.reportText
18208
18425
  });
18426
+ if (!opened.ok && opened.needsConfirmation !== void 0 && opts.approveWait !== true) {
18427
+ const flow = await runApprovalFlow(opts, client, bundleDir, {
18428
+ componentName,
18429
+ figmaFile,
18430
+ begin: () => client.begin({ componentName, figmaFile, entry: bundle.manifest.entry, files: bundle.files, report: bundle.reportText })
18431
+ });
18432
+ if (flow === void 0) return;
18433
+ opened = flow;
18434
+ }
18209
18435
  if (!opened.ok) {
18210
- if (opened.needsConsent !== void 0) {
18211
- fail(opts, ExitCode.ConfirmationRequired, {
18212
- error: `publishing this design system needs an acceptance of the publishing terms (version ${opened.needsConsent.termsVersion}) first`,
18213
- code: "publishing-terms-not-accepted",
18214
- remediation: `Read the terms, then run \`${tendrilCommand(`publish ${opts.bundleDir} --accept-terms --confirm-publish`)}\`. The acceptance covers this design system only.`
18215
- });
18216
- }
18217
18436
  if (opened.needsConfirmation !== void 0) {
18218
18437
  fail(opts, ExitCode.ConfirmationRequired, {
18219
18438
  error: `publishing ${JSON.stringify(opened.needsConfirmation.componentName)} for the first time is a decision a person makes, not a build step`,
18220
18439
  code: "first-publish-unconfirmed",
18221
- remediation: `A human runs \`${tendrilCommand(`publish ${opts.bundleDir} --confirm-publish`)}\` in their own terminal. Later publishes of this component need no flag.`
18440
+ remediation: "Approve it in your browser \u2014 run the publish again and open the link it prints."
18222
18441
  });
18223
18442
  }
18224
18443
  refuse2(opts, opened, "publish-refused");
18225
18444
  }
18226
18445
  const uploaded = [];
18227
18446
  for (const object of opened.value.plan.objects) {
18228
- const file = path52.join(bundleDir, object.relPath);
18229
- if (!existsSync42(file)) {
18447
+ const file = path54.join(bundleDir, object.relPath);
18448
+ if (!existsSync44(file)) {
18230
18449
  fail(opts, ExitCode.InputValidation, {
18231
18450
  error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
18232
18451
  code: "planned-file-missing",
@@ -18236,12 +18455,15 @@ async function runPublish(opts) {
18236
18455
  const sent = await client.upload({
18237
18456
  publicationId: opened.value.publicationId,
18238
18457
  relPath: object.relPath,
18239
- bytes: new Uint8Array(readFileSync39(file))
18458
+ bytes: new Uint8Array(readFileSync41(file))
18240
18459
  });
18241
18460
  if (!sent.ok) refuse2(opts, sent, "upload-refused");
18242
18461
  uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
18243
18462
  }
18244
18463
  const committed = await client.commit({ publicationId: opened.value.publicationId });
18464
+ if (committed.ok) {
18465
+ await endRunPresence(componentName);
18466
+ }
18245
18467
  if (!committed.ok) {
18246
18468
  if (committed.missing !== void 0 && committed.missing.length > 0) {
18247
18469
  fail(opts, ExitCode.General, {
@@ -18278,23 +18500,23 @@ async function runPublish(opts) {
18278
18500
  );
18279
18501
  }
18280
18502
  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)) {
18503
+ const manifestPath2 = path54.join(bundleDir, "component.json");
18504
+ const reportPath = path54.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
18505
+ if (!existsSync44(manifestPath2)) {
18284
18506
  fail(opts, ExitCode.InputValidation, {
18285
18507
  error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
18286
18508
  code: "not-a-bundle",
18287
18509
  remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
18288
18510
  });
18289
18511
  }
18290
- if (!existsSync42(reportPath)) {
18512
+ if (!existsSync44(reportPath)) {
18291
18513
  fail(opts, ExitCode.InputValidation, {
18292
18514
  error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
18293
18515
  code: "bundle-not-verified",
18294
18516
  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
18517
  });
18296
18518
  }
18297
- const { manifest } = readBundleManifest(readFileSync39(manifestPath2, "utf8"));
18519
+ const { manifest } = readBundleManifest(readFileSync41(manifestPath2, "utf8"));
18298
18520
  if (manifest === void 0) {
18299
18521
  fail(opts, ExitCode.InputValidation, {
18300
18522
  error: "component.json did not parse as a bundle manifest",
@@ -18302,7 +18524,7 @@ function readBundle(opts, bundleDir) {
18302
18524
  remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
18303
18525
  });
18304
18526
  }
18305
- const reportText = readFileSync39(reportPath, "utf8");
18527
+ const reportText = readFileSync41(reportPath, "utf8");
18306
18528
  let report;
18307
18529
  try {
18308
18530
  report = JSON.parse(reportText);
@@ -18362,6 +18584,96 @@ function refuse2(opts, sent, code) {
18362
18584
  remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${(opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "")}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 a retry rejoins this same unfinished publication.` : `Fix what is named above and run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication rather than starting a second one.`
18363
18585
  });
18364
18586
  }
18587
+ function pendingApprovalPath() {
18588
+ return path54.join(path54.dirname(sessionPath()), "pending-publish.json");
18589
+ }
18590
+ async function runApprovalFlow(opts, client, bundleDir, input) {
18591
+ const requested = await client.requestApproval({ componentName: input.componentName, figmaFile: input.figmaFile });
18592
+ if (!requested.ok) refuse2(opts, requested, "approval-request-refused");
18593
+ const approval = requested.value;
18594
+ if (opts.approveStart === true) {
18595
+ writeFileSync23(pendingApprovalPath(), `${JSON.stringify({ ...approval, bundleDir }, null, 2)}
18596
+ `, { mode: 384 });
18597
+ emitData(
18598
+ opts,
18599
+ {
18600
+ status: "approval-pending",
18601
+ approveUrl: approval.approveUrl,
18602
+ componentName: input.componentName,
18603
+ expiresAt: approval.expiresAt,
18604
+ next: "open the approve page in the signed-in browser, click Approve (tick the terms box if shown), then finish with --approve-wait"
18605
+ },
18606
+ () => {
18607
+ process.stdout.write(`Approval requested for ${JSON.stringify(input.componentName)}.
18608
+ `);
18609
+ process.stdout.write(`Approve it here: ${approval.approveUrl}
18610
+ `);
18611
+ process.stdout.write(`Then finish with: ${tendrilCommand(`publish ${opts.bundleDir} --approve-wait`)}
18612
+ `);
18613
+ }
18614
+ );
18615
+ return void 0;
18616
+ }
18617
+ process.stderr.write(`This component's FIRST publish needs your approval in the browser:
18618
+ ${approval.approveUrl}
18619
+ `);
18620
+ process.stderr.write(`Waiting for your decision (lapses at ${approval.expiresAt.slice(11, 16)} UTC)\u2026
18621
+ `);
18622
+ const decided = await waitForApproval(opts, client, approval);
18623
+ if (decided === "approved") return input.begin();
18624
+ failDecision(opts, decided);
18625
+ }
18626
+ async function approveWaitPhase(opts, client, bundleDir) {
18627
+ const file = pendingApprovalPath();
18628
+ let pending;
18629
+ if (existsSync44(file)) {
18630
+ try {
18631
+ const parsed = JSON.parse(readFileSync41(file, "utf8"));
18632
+ if (typeof parsed.approvalId === "string" && typeof parsed.approveUrl === "string" && typeof parsed.expiresAt === "string") {
18633
+ pending = { pollSeconds: 3, bundleDir, ...parsed };
18634
+ }
18635
+ } catch {
18636
+ }
18637
+ }
18638
+ if (pending === void 0) {
18639
+ fail(opts, ExitCode.InputValidation, {
18640
+ error: "there is no publish approval waiting to finish",
18641
+ code: "no-pending-approval",
18642
+ remediation: `Start one first: ${tendrilCommand(`publish ${opts.bundleDir} --approve-start`)} (the tendril_publish tool).`
18643
+ });
18644
+ }
18645
+ const done = () => rmSync8(file, { force: true });
18646
+ const decided = await waitForApproval(opts, client, pending);
18647
+ done();
18648
+ if (decided !== "approved") failDecision(opts, decided);
18649
+ }
18650
+ async function waitForApproval(opts, client, approval) {
18651
+ const interval = Math.max(1, approval.pollSeconds) * 1e3;
18652
+ const total = Math.ceil(APPROVAL_WAIT_CAP_MS / interval);
18653
+ for (let tick = 1; tick <= total; tick += 1) {
18654
+ const polled = await client.pollApproval({ approvalId: approval.approvalId });
18655
+ if (!polled.ok) refuse2(opts, polled, "approval-poll-refused");
18656
+ if (polled.value.status !== "pending") return polled.value.status;
18657
+ emitProgress(tick, total, "waiting for the browser approval");
18658
+ await new Promise((resolve) => setTimeout(resolve, interval));
18659
+ }
18660
+ return "expired";
18661
+ }
18662
+ function failDecision(opts, decided) {
18663
+ if (decided === "denied") {
18664
+ fail(opts, ExitCode.ConfirmationRequired, {
18665
+ error: "the publish was DENIED in the browser \u2014 the human said no",
18666
+ code: "publish-approval-denied",
18667
+ remediation: "Nothing was published. If minds change, run the publish again \u2014 it makes a fresh request."
18668
+ });
18669
+ }
18670
+ fail(opts, ExitCode.ConfirmationRequired, {
18671
+ error: decided === "expired" ? "the approval request lapsed before anyone decided it" : "the approval request is gone \u2014 it lapsed and was cleaned up, or was already spent",
18672
+ code: "publish-approval-lapsed",
18673
+ remediation: "Run the publish again for a fresh request, and decide it within its 30-minute window."
18674
+ });
18675
+ }
18676
+ var APPROVAL_WAIT_CAP_MS;
18365
18677
  var init_publish = __esm({
18366
18678
  "packages/cli/src/commands/publish.ts"() {
18367
18679
  "use strict";
@@ -18370,6 +18682,8 @@ var init_publish = __esm({
18370
18682
  init_invocation();
18371
18683
  init_output();
18372
18684
  init_publish_client();
18685
+ init_run_presence();
18686
+ APPROVAL_WAIT_CAP_MS = 31 * 6e4;
18373
18687
  }
18374
18688
  });
18375
18689
 
@@ -18397,17 +18711,17 @@ __export(generate_recorded_exports, {
18397
18711
  runGenerateRecorded: () => runGenerateRecorded
18398
18712
  });
18399
18713
  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";
18714
+ import { existsSync as existsSync45, readFileSync as readFileSync42 } from "node:fs";
18715
+ import path55 from "node:path";
18402
18716
  async function runGenerateRecorded(opts) {
18403
18717
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
18404
- const outDirAbs = path53.resolve(callerCwd, opts.out);
18405
- const recordedAsPath = path53.resolve(callerCwd, opts.recorded);
18718
+ const outDirAbs = path55.resolve(callerCwd, opts.out);
18719
+ const recordedAsPath = path55.resolve(callerCwd, opts.recorded);
18406
18720
  let task;
18407
18721
  let taskName;
18408
18722
  let authoredApi;
18409
18723
  let composition;
18410
- const isSet = existsSync43(path53.join(recordedAsPath, "recording-set.json"));
18724
+ const isSet = existsSync45(path55.join(recordedAsPath, "recording-set.json"));
18411
18725
  const registry = TASKS[opts.recorded];
18412
18726
  if (registry !== void 0 && !isSet) {
18413
18727
  task = registry;
@@ -18416,7 +18730,7 @@ async function runGenerateRecorded(opts) {
18416
18730
  try {
18417
18731
  const authored = authorTaskFromSet(recordedAsPath);
18418
18732
  task = authored.task;
18419
- taskName = path53.basename(recordedAsPath);
18733
+ taskName = path55.basename(recordedAsPath);
18420
18734
  authoredApi = authored.api;
18421
18735
  const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
18422
18736
  if (roles.success) composition = roles.data;
@@ -18450,7 +18764,7 @@ async function runGenerateRecorded(opts) {
18450
18764
  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
18765
  }
18452
18766
  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"))
18767
+ (c) => !existsSync45(path55.join(task.set, c.rep, "get_screenshot.json")) || !existsSync45(path55.join(task.set, c.rep, "get_metadata.json")) || !existsSync45(path55.join(task.set, c.rep, "get_design_context.json"))
18454
18768
  );
18455
18769
  if (missing.length > 0) {
18456
18770
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -18520,8 +18834,8 @@ async function runGenerateRecorded(opts) {
18520
18834
  ` : `${line}
18521
18835
  `);
18522
18836
  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)})
18837
+ emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path55.join(outDirAbs, taskName) }, () => {
18838
+ process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path55.join(outDirAbs, taskName)})
18525
18839
  `);
18526
18840
  });
18527
18841
  return;
@@ -18544,10 +18858,10 @@ async function runGenerateRecorded(opts) {
18544
18858
  });
18545
18859
  }
18546
18860
  }
18547
- const bundleDir = path53.join(outDirAbs, taskName);
18548
- if (existsSync43(path53.join(bundleDir, "component.json"))) {
18861
+ const bundleDir = path55.join(outDirAbs, taskName);
18862
+ if (existsSync45(path55.join(bundleDir, "component.json"))) {
18549
18863
  try {
18550
- const prior = readBundleManifest(readFileSync40(path53.join(bundleDir, "component.json"), "utf8")).manifest;
18864
+ const prior = readBundleManifest(readFileSync42(path55.join(bundleDir, "component.json"), "utf8")).manifest;
18551
18865
  if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
18552
18866
  fail(opts, ExitCode.InputValidation, {
18553
18867
  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 +19028,7 @@ init_invocation();
18714
19028
  init_output();
18715
19029
  import { intro, isCancel, outro, password } from "@clack/prompts";
18716
19030
  import fs from "node:fs";
18717
- import path29 from "node:path";
19031
+ import path30 from "node:path";
18718
19032
  var INIT_DESCRIPTION = {
18719
19033
  name: "init",
18720
19034
  summary: "Configure the OpenRouter credential in .env, and optionally a Figma token (idempotent).",
@@ -18756,7 +19070,7 @@ async function runInit(flags) {
18756
19070
  printDescription(INIT_DESCRIPTION);
18757
19071
  return;
18758
19072
  }
18759
- const envPath = path29.resolve(process.cwd(), ".env");
19073
+ const envPath = path30.resolve(process.cwd(), ".env");
18760
19074
  const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
18761
19075
  let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
18762
19076
  let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
@@ -18777,7 +19091,7 @@ async function runInit(flags) {
18777
19091
  if (openrouterKey) next.set(ENV_KEYS.openrouter, openrouterKey);
18778
19092
  if (figmaToken) next.set(ENV_KEYS.figma, figmaToken);
18779
19093
  const changed = [...next].some(([key, value]) => existing.get(key) !== value);
18780
- const gitignorePath = path29.resolve(process.cwd(), ".gitignore");
19094
+ const gitignorePath = path30.resolve(process.cwd(), ".gitignore");
18781
19095
  const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
18782
19096
  const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
18783
19097
  if (flags.dryRun) {
@@ -18833,14 +19147,14 @@ init_invocation();
18833
19147
  init_output();
18834
19148
  init_entitlement();
18835
19149
  import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
18836
- import { readFileSync as readFileSync21, readdirSync as readdirSync9, existsSync as existsSync23 } from "node:fs";
19150
+ import { readFileSync as readFileSync22, readdirSync as readdirSync9, existsSync as existsSync24 } from "node:fs";
18837
19151
 
18838
19152
  // packages/cli/src/pipeline.ts
18839
19153
  init_src2();
18840
19154
  init_src5();
18841
19155
  init_src4();
18842
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "node:fs";
18843
- import path30 from "node:path";
19156
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "node:fs";
19157
+ import path31 from "node:path";
18844
19158
 
18845
19159
  // packages/cli/src/assets-module.ts
18846
19160
  init_src();
@@ -19176,7 +19490,7 @@ async function runGenerationPipeline(input) {
19176
19490
  });
19177
19491
  const written = [];
19178
19492
  if (!input.dryRun) {
19179
- const dir = path30.resolve(input.outDir, semantics.componentName);
19493
+ const dir = path31.resolve(input.outDir, semantics.componentName);
19180
19494
  mkdirSync6(dir, { recursive: true });
19181
19495
  const files = {
19182
19496
  // Bundle-local tokens: THE emission the component's CSS resolves
@@ -19200,14 +19514,14 @@ async function runGenerationPipeline(input) {
19200
19514
  `
19201
19515
  };
19202
19516
  for (const [name, content] of Object.entries(files)) {
19203
- const filePath = path30.join(dir, name);
19204
- writeFileSync9(filePath, content);
19517
+ const filePath = path31.join(dir, name);
19518
+ writeFileSync10(filePath, content);
19205
19519
  written.push(filePath);
19206
19520
  }
19207
19521
  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);
19522
+ const filePath = path31.resolve(input.outDir, artifact.path);
19523
+ mkdirSync6(path31.dirname(filePath), { recursive: true });
19524
+ writeFileSync10(filePath, artifact.content);
19211
19525
  written.push(filePath);
19212
19526
  }
19213
19527
  }
@@ -19265,7 +19579,7 @@ var GENERATE_DESCRIPTION = {
19265
19579
  function resolveProvidedSource(flags, contextFile) {
19266
19580
  let raw;
19267
19581
  try {
19268
- raw = readFileSync21(contextFile, "utf8");
19582
+ raw = readFileSync22(contextFile, "utf8");
19269
19583
  } catch {
19270
19584
  fail(flags, ExitCode.InputValidation, {
19271
19585
  error: `Cannot read context file "${contextFile}".`,
@@ -19385,11 +19699,11 @@ token mapping (${mapping.flat.length} variables):
19385
19699
  let initialCode;
19386
19700
  let initialSemantics;
19387
19701
  try {
19388
- if (existsSync23(flags.out)) {
19702
+ if (existsSync24(flags.out)) {
19389
19703
  for (const entry of readdirSync9(flags.out)) {
19390
19704
  const cjPath = `${flags.out}/${entry}/component.json`;
19391
- if (!existsSync23(cjPath)) continue;
19392
- const cj = JSON.parse(readFileSync21(cjPath, "utf8"));
19705
+ if (!existsSync24(cjPath)) continue;
19706
+ const cj = JSON.parse(readFileSync22(cjPath, "utf8"));
19393
19707
  if (cj.name !== void 0 && Array.isArray(cj.props)) {
19394
19708
  previousApi = JSON.stringify({
19395
19709
  componentName: cj.name,
@@ -19397,14 +19711,14 @@ token mapping (${mapping.flat.length} variables):
19397
19711
  });
19398
19712
  const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
19399
19713
  const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
19400
- if (flags.refine && existsSync23(tsxPath) && existsSync23(cssPath)) {
19714
+ if (flags.refine && existsSync24(tsxPath) && existsSync24(cssPath)) {
19401
19715
  initialCode = {
19402
- tsx: readFileSync21(tsxPath, "utf8"),
19403
- css: readFileSync21(cssPath, "utf8")
19716
+ tsx: readFileSync22(tsxPath, "utf8"),
19717
+ css: readFileSync22(cssPath, "utf8")
19404
19718
  };
19405
19719
  const semPath = `${flags.out}/${entry}/semantics.json`;
19406
- if (existsSync23(semPath)) {
19407
- initialSemantics = JSON.parse(readFileSync21(semPath, "utf8"));
19720
+ if (existsSync24(semPath)) {
19721
+ initialSemantics = JSON.parse(readFileSync22(semPath, "utf8"));
19408
19722
  }
19409
19723
  }
19410
19724
  break;
@@ -19544,6 +19858,15 @@ bundle written to ${flags.out}/${result.semantics.componentName}/:
19544
19858
 
19545
19859
  // packages/cli/src/program.ts
19546
19860
  init_src5();
19861
+ function parseHoverTimeout(raw) {
19862
+ const ms = Number(raw);
19863
+ if (!Number.isInteger(ms) || ms < 100 || ms > 6e4) {
19864
+ process.stderr.write(`--hover-timeout must be an integer between 100 and 60000 milliseconds, got "${raw}"
19865
+ `);
19866
+ process.exit(2);
19867
+ }
19868
+ return ms;
19869
+ }
19547
19870
  function globalFlags(cmd) {
19548
19871
  const opts = cmd.optsWithGlobals();
19549
19872
  return {
@@ -19735,7 +20058,7 @@ function buildProgram() {
19735
20058
  ...local["profile"] !== void 0 ? { profile: local["profile"] } : {}
19736
20059
  });
19737
20060
  });
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) => {
20061
+ 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
20062
  const flags = globalFlags(cmd.parent.parent);
19740
20063
  const local = cmd.opts();
19741
20064
  const { runEngineScore: runEngineScore2 } = await Promise.resolve().then(() => (init_engine2(), engine_exports));
@@ -19747,7 +20070,8 @@ function buildProgram() {
19747
20070
  ...local["host"] !== void 0 ? { host: local["host"] } : {},
19748
20071
  model: local["model"],
19749
20072
  rebind: local["rebind"],
19750
- ...local["library"] !== void 0 ? { library: local["library"] } : {}
20073
+ ...local["library"] !== void 0 ? { library: local["library"] } : {},
20074
+ ...local["hoverTimeout"] !== void 0 ? { hoverTimeoutMs: parseHoverTimeout(local["hoverTimeout"]) } : {}
19751
20075
  });
19752
20076
  });
19753
20077
  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) => {
@@ -19830,7 +20154,7 @@ function buildProgram() {
19830
20154
  ...local["revoke"] !== void 0 ? { revoke: local["revoke"] } : {}
19831
20155
  });
19832
20156
  });
19833
- program.command("publish").description("Put a verified bundle and its evidence on a page, at a URL you can send to someone. Refuses a run the ruler declined.").argument("<bundleDir>", "bundle directory that has already been verified").option("--to <url>", "the portal to publish to (or set TENDRIL_PORTAL_URL)").option("--name <name>", "library label for this component (default: the bundle manifest's own name)").option("--accept-terms", "accept the current publishing terms for this design system as part of this run").option("--confirm-publish", "you, a person, saying this component may be published \u2014 required the first time, and refused without an interactive terminal").action(async (bundleDir, _o, cmd) => {
20157
+ program.command("publish").description("Put a verified bundle and its evidence on a page, at a URL you can send to someone. Refuses a run the ruler declined.").argument("<bundleDir>", "bundle directory that has already been verified").option("--to <url>", "the portal to publish to (or set TENDRIL_PORTAL_URL)").option("--name <name>", "library label for this component (default: the bundle manifest's own name)").option("--approve-start", "first publish only: request the browser approval, print the link, persist the pending state and exit \u2014 the tendril_publish tool's phase one").option("--approve-wait", "resume a pending approval: poll until the human decides in the browser, then publish \u2014 phase two").action(async (bundleDir, _o, cmd) => {
19834
20158
  const flags = globalFlags(cmd.parent);
19835
20159
  const local = cmd.opts();
19836
20160
  const { runPublish: runPublish2 } = await Promise.resolve().then(() => (init_publish(), publish_exports));
@@ -19839,11 +20163,11 @@ function buildProgram() {
19839
20163
  bundleDir,
19840
20164
  ...local["to"] !== void 0 ? { to: local["to"] } : {},
19841
20165
  ...local["name"] !== void 0 ? { name: local["name"] } : {},
19842
- ...local["acceptTerms"] !== void 0 ? { acceptTerms: local["acceptTerms"] } : {},
19843
- ...local["confirmPublish"] !== void 0 ? { confirmPublish: local["confirmPublish"] } : {}
20166
+ ...local["approveStart"] !== void 0 ? { approveStart: local["approveStart"] } : {},
20167
+ ...local["approveWait"] !== void 0 ? { approveWait: local["approveWait"] } : {}
19844
20168
  });
19845
20169
  });
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) => {
20170
+ 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
20171
  const flags = globalFlags(cmd);
19848
20172
  const local = cmd.opts();
19849
20173
  const bar = local["bar"] === "cert" ? "cert" : "pass";
@@ -19855,7 +20179,8 @@ function buildProgram() {
19855
20179
  ...local["set"] !== void 0 ? { set: local["set"] } : {},
19856
20180
  ...local["library"] !== void 0 ? { library: local["library"] } : {},
19857
20181
  bar,
19858
- ...local["profile"] !== void 0 ? { profile: local["profile"] } : {}
20182
+ ...local["profile"] !== void 0 ? { profile: local["profile"] } : {},
20183
+ ...local["hoverTimeout"] !== void 0 ? { hoverTimeoutMs: parseHoverTimeout(local["hoverTimeout"]) } : {}
19859
20184
  });
19860
20185
  });
19861
20186
  program.command("generate").description(