@tendrilapp/cli 0.1.23 → 0.1.25

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
@@ -14,11 +14,11 @@ var __export = (target, all) => {
14
14
  __defProp(target, name, { get: all[name], enumerable: true });
15
15
  };
16
16
 
17
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/core/src/ir.ts
17
+ // packages/core/src/ir.ts
18
18
  import { z } from "zod";
19
19
  var IrLayoutSchema, IrMotionSchema, IrPositionSchema, IrNodeSchema, ComponentIrSchema;
20
20
  var init_ir = __esm({
21
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/core/src/ir.ts"() {
21
+ "packages/core/src/ir.ts"() {
22
22
  "use strict";
23
23
  IrLayoutSchema = z.object({
24
24
  mode: z.enum(["flex-row", "flex-column", "grid", "none"]),
@@ -110,14 +110,14 @@ var init_ir = __esm({
110
110
  }
111
111
  });
112
112
 
113
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/source.ts
113
+ // packages/figma/src/source.ts
114
114
  var init_source = __esm({
115
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/source.ts"() {
115
+ "packages/figma/src/source.ts"() {
116
116
  "use strict";
117
117
  }
118
118
  });
119
119
 
120
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/svg-safety.ts
120
+ // packages/figma/src/svg-safety.ts
121
121
  function checkSvgSafety(source) {
122
122
  const issues = [];
123
123
  if (!/<\s*svg\b/i.test(source)) issues.push("not an <svg> document");
@@ -132,7 +132,7 @@ function assetExportName(key) {
132
132
  }
133
133
  var UNSAFE_PATTERNS;
134
134
  var init_svg_safety = __esm({
135
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/svg-safety.ts"() {
135
+ "packages/figma/src/svg-safety.ts"() {
136
136
  "use strict";
137
137
  UNSAFE_PATTERNS = [
138
138
  [/<\s*script\b/i, "script element"],
@@ -146,11 +146,11 @@ var init_svg_safety = __esm({
146
146
  }
147
147
  });
148
148
 
149
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/provided.ts
149
+ // packages/figma/src/provided.ts
150
150
  import { z as z2 } from "zod";
151
151
  var ProvidedVariableSchema, RangeSchema, PinnedPlacementSchema, VariantValueFactsSchema, ProvidedMetadataSchema, ProvidedContextSchema, ProvidedContextSource;
152
152
  var init_provided = __esm({
153
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/provided.ts"() {
153
+ "packages/figma/src/provided.ts"() {
154
154
  "use strict";
155
155
  init_svg_safety();
156
156
  ProvidedVariableSchema = z2.object({
@@ -269,7 +269,7 @@ var init_provided = __esm({
269
269
  }
270
270
  });
271
271
 
272
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mcp/client.ts
272
+ // packages/figma/src/mcp/client.ts
273
273
  function parseMessages(body, contentType) {
274
274
  if (body.trim() === "") return [];
275
275
  if (!contentType.includes("text/event-stream")) {
@@ -285,7 +285,7 @@ function parseMessages(body, contentType) {
285
285
  }
286
286
  var PROTOCOL_VERSION, McpUnavailableError, McpProtocolError, McpHttpClient;
287
287
  var init_client = __esm({
288
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mcp/client.ts"() {
288
+ "packages/figma/src/mcp/client.ts"() {
289
289
  "use strict";
290
290
  PROTOCOL_VERSION = "2025-06-18";
291
291
  McpUnavailableError = class extends Error {
@@ -389,7 +389,7 @@ var init_client = __esm({
389
389
  }
390
390
  });
391
391
 
392
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mcp/normalize.ts
392
+ // packages/figma/src/mcp/normalize.ts
393
393
  function decodeXmlEntities(v) {
394
394
  return v.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&");
395
395
  }
@@ -472,7 +472,7 @@ function mergeVariantAxes(childNames) {
472
472
  }
473
473
  var TAG_TO_TYPE, TAG_RE, ATTR_RE;
474
474
  var init_normalize = __esm({
475
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mcp/normalize.ts"() {
475
+ "packages/figma/src/mcp/normalize.ts"() {
476
476
  "use strict";
477
477
  init_svg_safety();
478
478
  TAG_TO_TYPE = {
@@ -488,10 +488,10 @@ var init_normalize = __esm({
488
488
  }
489
489
  });
490
490
 
491
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mock/fixtures/button.ts
491
+ // packages/figma/src/mock/fixtures/button.ts
492
492
  var buttonVariables, buttonMetadata, buttonNodeTree;
493
493
  var init_button = __esm({
494
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mock/fixtures/button.ts"() {
494
+ "packages/figma/src/mock/fixtures/button.ts"() {
495
495
  "use strict";
496
496
  buttonVariables = [
497
497
  {
@@ -600,10 +600,10 @@ var init_button = __esm({
600
600
  }
601
601
  });
602
602
 
603
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mock/mock-source.ts
603
+ // packages/figma/src/mock/mock-source.ts
604
604
  var MockFigmaSource;
605
605
  var init_mock_source = __esm({
606
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mock/mock-source.ts"() {
606
+ "packages/figma/src/mock/mock-source.ts"() {
607
607
  "use strict";
608
608
  init_button();
609
609
  MockFigmaSource = class {
@@ -640,7 +640,7 @@ var init_mock_source = __esm({
640
640
  }
641
641
  });
642
642
 
643
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/css-ident.ts
643
+ // packages/figma/src/css-ident.ts
644
644
  function canonicalCssIdentPart(part) {
645
645
  return part.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
646
646
  }
@@ -649,27 +649,27 @@ function variableNameToCssVar(name) {
649
649
  return `--${parts.join("-")}`;
650
650
  }
651
651
  var init_css_ident = __esm({
652
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/css-ident.ts"() {
652
+ "packages/figma/src/css-ident.ts"() {
653
653
  "use strict";
654
654
  }
655
655
  });
656
656
 
657
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mcp/var-decoder.ts
657
+ // packages/figma/src/mcp/var-decoder.ts
658
658
  var init_var_decoder = __esm({
659
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mcp/var-decoder.ts"() {
659
+ "packages/figma/src/mcp/var-decoder.ts"() {
660
660
  "use strict";
661
661
  init_css_ident();
662
662
  }
663
663
  });
664
664
 
665
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mcp/font-normalize.ts
665
+ // packages/figma/src/mcp/font-normalize.ts
666
666
  var init_font_normalize = __esm({
667
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/mcp/font-normalize.ts"() {
667
+ "packages/figma/src/mcp/font-normalize.ts"() {
668
668
  "use strict";
669
669
  }
670
670
  });
671
671
 
672
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/provided-recording.ts
672
+ // packages/figma/src/provided-recording.ts
673
673
  import { z as z3 } from "zod";
674
674
  function checkPngPayload(base64) {
675
675
  let raw;
@@ -684,7 +684,7 @@ function checkPngPayload(base64) {
684
684
  }
685
685
  var MAX_EMISSION_BYTES, MAX_IMAGE_BYTES, MAX_ASSET_BYTES, PNG_MAGIC, TextEnvelopeSchema, ImageEnvelopeSchema, ProvidedRepSchema, ManifestWireSchema, ProvidedRecordingSetSchema;
686
686
  var init_provided_recording = __esm({
687
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/provided-recording.ts"() {
687
+ "packages/figma/src/provided-recording.ts"() {
688
688
  "use strict";
689
689
  init_svg_safety();
690
690
  MAX_EMISSION_BYTES = 2 * 1024 * 1024;
@@ -752,7 +752,7 @@ var init_provided_recording = __esm({
752
752
  }
753
753
  });
754
754
 
755
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/recording/axis-defaults.ts
755
+ // packages/figma/src/recording/axis-defaults.ts
756
756
  function resolveAxisDefaultWithRule(domain, recorded, override) {
757
757
  if (override !== void 0) return { value: override, rule: "override" };
758
758
  const literal = domain.find((v) => kebab(v) === "default" || kebab(v) === "enabled");
@@ -787,7 +787,7 @@ function resolveAxisDefault(domain, recorded, override) {
787
787
  }
788
788
  var INTERACTION_STATES, BOOLEAN_STATES, ENGAGED_STATES, INTERACTION_EVIDENCE_VALUES, kebab, OFF_VALUES, ON_VALUES;
789
789
  var init_axis_defaults = __esm({
790
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/recording/axis-defaults.ts"() {
790
+ "packages/figma/src/recording/axis-defaults.ts"() {
791
791
  "use strict";
792
792
  INTERACTION_STATES = /* @__PURE__ */ new Set(["hover", "focus", "focus-visible", "active", "pressed"]);
793
793
  BOOLEAN_STATES = /* @__PURE__ */ new Set(["disabled", "loading"]);
@@ -810,7 +810,7 @@ var init_axis_defaults = __esm({
810
810
  }
811
811
  });
812
812
 
813
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/recording/plan.ts
813
+ // packages/figma/src/recording/plan.ts
814
814
  function singleAxes(name) {
815
815
  const parsed = parseVariantAxes(name);
816
816
  if (parsed === void 0) return void 0;
@@ -920,7 +920,7 @@ function planQueue(symbols, opts = {}) {
920
920
  }
921
921
  var kebab2;
922
922
  var init_plan = __esm({
923
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/recording/plan.ts"() {
923
+ "packages/figma/src/recording/plan.ts"() {
924
924
  "use strict";
925
925
  init_normalize();
926
926
  init_axis_defaults();
@@ -928,7 +928,75 @@ var init_plan = __esm({
928
928
  }
929
929
  });
930
930
 
931
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/recording/session.ts
931
+ // packages/figma/src/recording/envelope-content.ts
932
+ function envelopeParts(payload) {
933
+ const content = payload?.content;
934
+ if (!Array.isArray(content)) return null;
935
+ const blocks = content;
936
+ const text = blocks.map((b) => typeof b.text === "string" ? b.text : "").filter((t) => t !== "").join("\n");
937
+ const image = blocks.find((b) => b.type === "image" && typeof b.data === "string");
938
+ return image === void 0 ? { text } : { text, imageData: image.data };
939
+ }
940
+ function namedCause(text, table) {
941
+ return table.find((entry) => entry.pattern.test(text))?.cause;
942
+ }
943
+ function jsonObjectPayload(text) {
944
+ try {
945
+ const value = JSON.parse(text);
946
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
947
+ } catch {
948
+ return null;
949
+ }
950
+ }
951
+ function checkEnvelopeContent(tool, payload) {
952
+ const parts = envelopeParts(payload);
953
+ if (parts === null) return { ok: true };
954
+ const { text, imageData } = parts;
955
+ const serviceError = namedCause(text, SERVICE_ERROR_SIGNATURES);
956
+ if (serviceError !== void 0) return { ok: false, reason: serviceError };
957
+ const reject = (expected) => {
958
+ const hint = namedCause(text, FAILURE_HINTS);
959
+ return { ok: false, reason: hint === void 0 ? expected : `${hint} \u2014 ${expected}` };
960
+ };
961
+ switch (tool) {
962
+ case "get_metadata":
963
+ case "get_metadata_interior":
964
+ return NODE_MARKUP.test(text) ? { ok: true } : reject('no node markup in the response (a get_metadata response contains elements like <symbol id="\u2026"> or <frame id="\u2026">)');
965
+ case "get_design_context":
966
+ return CODE_DECLARATION.test(text) || ANY_MARKUP.test(text) ? { ok: true } : reject("no code emission or markup in the response (a get_design_context response contains a component's code, or node markup when the design is too large)");
967
+ case "get_variable_defs":
968
+ return jsonObjectPayload(text) === null ? reject("the response is not a JSON object of variable definitions") : { ok: true };
969
+ case "get_screenshot": {
970
+ if (imageData === void 0) return reject("no image block in the response (a get_screenshot envelope carries base64 PNG bytes)");
971
+ const png = checkPngPayload(imageData);
972
+ return png.ok ? { ok: true } : reject(png.reason);
973
+ }
974
+ }
975
+ }
976
+ var NODE_MARKUP, ANY_MARKUP, CODE_DECLARATION, SERVICE_ERROR_SIGNATURES, FAILURE_HINTS;
977
+ var init_envelope_content = __esm({
978
+ "packages/figma/src/recording/envelope-content.ts"() {
979
+ "use strict";
980
+ init_provided_recording();
981
+ NODE_MARKUP = /<[A-Za-z][\w:.-]*(?:\s[^>]*)?\sid="[^"]+"/;
982
+ ANY_MARKUP = /<[A-Za-z][\w:.-]*(?:\s[^>]*)?\/?>/;
983
+ CODE_DECLARATION = /(?:^|\n)[ \t]*(?:const|let|var|function|class|export|import|type|interface|async|@)\b/;
984
+ SERVICE_ERROR_SIGNATURES = [
985
+ { pattern: /reached the .{0,60}tool call limit/i, cause: "the Figma MCP reported that the account's tool-call limit is exhausted" },
986
+ { pattern: /tool call limit for your/i, cause: "the Figma MCP reported that the account's tool-call limit is exhausted" },
987
+ { pattern: /upgrade your (?:seat|plan)/i, cause: "the Figma MCP returned an upgrade/quota notice" }
988
+ ];
989
+ FAILURE_HINTS = [
990
+ { pattern: /\brate[- ]limit/i, cause: "the response looks like a rate-limit error" },
991
+ { pattern: /\b(?:429|too many requests)\b/i, cause: "the response looks like a rate-limit error" },
992
+ { pattern: /\b(?:quota|billing|subscription|seat)\b/i, cause: "the response looks like a quota/plan notice" },
993
+ { pattern: /\b(?:unauthorized|forbidden|not authorized|access denied|401|403)\b/i, cause: "the response looks like an authorization error" },
994
+ { pattern: /\b(?:error|failed|failure|timed out|timeout)\b/i, cause: "the response looks like an error message" }
995
+ ];
996
+ }
997
+ });
998
+
999
+ // packages/figma/src/recording/session.ts
932
1000
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
933
1001
  import path from "node:path";
934
1002
  import { z as z4 } from "zod";
@@ -1036,10 +1104,14 @@ function ingestEnvelope(setDir, slug, tool, payload) {
1036
1104
  if (!RECORD_TOOLS.includes(tool)) {
1037
1105
  throw new Error(`unknown tool "${tool}" \u2014 expected one of ${RECORD_TOOLS.join(", ")}`);
1038
1106
  }
1107
+ const content = checkEnvelopeContent(tool, payload);
1108
+ if (!content.ok) {
1109
+ throw new Error(`${slug}/${tool} was NOT recorded \u2014 ${content.reason}. ${REINGEST_GUIDANCE}`);
1110
+ }
1039
1111
  const schema = tool === "get_screenshot" ? ImageEnvelopeSchema : TextEnvelopeSchema;
1040
1112
  const parsed = schema.safeParse(payload);
1041
1113
  if (!parsed.success) {
1042
- throw new Error(`${slug}/${tool}: envelope rejected at the boundary: ${parsed.error.issues[0]?.message ?? "invalid"}`);
1114
+ throw new Error(`${slug}/${tool} was NOT recorded \u2014 envelope rejected at the boundary: ${parsed.error.issues[0]?.message ?? "invalid"}. ${REINGEST_GUIDANCE}`);
1043
1115
  }
1044
1116
  const file = containedPath(setDir, slug, `${tool}.json`);
1045
1117
  mkdirSync(path.dirname(file), { recursive: true });
@@ -1076,12 +1148,13 @@ function ingestAsset(setDir, slug, name, content) {
1076
1148
  mkdirSync(path.dirname(file), { recursive: true });
1077
1149
  writeFileSync(file, content);
1078
1150
  }
1079
- var RECORD_TOOLS, INTERIOR_TOOL, REQUIRED_TOOLS, SessionManifestSchema, manifestPath, PROTOCOL_ORDER, byProtocolOrder;
1151
+ var RECORD_TOOLS, INTERIOR_TOOL, REQUIRED_TOOLS, SessionManifestSchema, manifestPath, PROTOCOL_ORDER, byProtocolOrder, REINGEST_GUIDANCE;
1080
1152
  var init_session = __esm({
1081
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/recording/session.ts"() {
1153
+ "packages/figma/src/recording/session.ts"() {
1082
1154
  "use strict";
1083
1155
  init_svg_safety();
1084
1156
  init_provided_recording();
1157
+ init_envelope_content();
1085
1158
  init_plan();
1086
1159
  RECORD_TOOLS = ["get_design_context", "get_metadata", "get_screenshot", "get_variable_defs", "get_metadata_interior"];
1087
1160
  INTERIOR_TOOL = "get_metadata_interior";
@@ -1120,10 +1193,11 @@ var init_session = __esm({
1120
1193
  manifestPath = (setDir) => path.join(setDir, "recording-set.json");
1121
1194
  PROTOCOL_ORDER = ["get_metadata", "get_design_context", "get_screenshot", "get_variable_defs", "get_metadata_interior"];
1122
1195
  byProtocolOrder = (tools) => [...tools].sort((a, b) => PROTOCOL_ORDER.indexOf(a) - PROTOCOL_ORDER.indexOf(b));
1196
+ REINGEST_GUIDANCE = "Nothing was written. Recording persists per rep and resumes from disk, so re-run the same command with the real response; if the Figma MCP reported a tool-call or rate limit, wait for the quota to reset first.";
1123
1197
  }
1124
1198
  });
1125
1199
 
1126
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/recording/roles.ts
1200
+ // packages/figma/src/recording/roles.ts
1127
1201
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
1128
1202
  import path2 from "node:path";
1129
1203
  function emissionIdSets(emission) {
@@ -1192,16 +1266,16 @@ function deriveRoles(setDir, manifest) {
1192
1266
  }
1193
1267
  var NODE_ID_RE;
1194
1268
  var init_roles = __esm({
1195
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/recording/roles.ts"() {
1269
+ "packages/figma/src/recording/roles.ts"() {
1196
1270
  "use strict";
1197
1271
  init_session();
1198
1272
  NODE_ID_RE = /data-node-id="([^"]+)"/g;
1199
1273
  }
1200
1274
  });
1201
1275
 
1202
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/index.ts
1276
+ // packages/figma/src/index.ts
1203
1277
  var init_src = __esm({
1204
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/figma/src/index.ts"() {
1278
+ "packages/figma/src/index.ts"() {
1205
1279
  "use strict";
1206
1280
  init_source();
1207
1281
  init_provided();
@@ -1216,16 +1290,17 @@ var init_src = __esm({
1216
1290
  init_axis_defaults();
1217
1291
  init_plan();
1218
1292
  init_session();
1293
+ init_envelope_content();
1219
1294
  init_roles();
1220
1295
  }
1221
1296
  });
1222
1297
 
1223
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/tokens/src/dtcg.ts
1298
+ // packages/tokens/src/dtcg.ts
1224
1299
  function variableNameToPath(name) {
1225
1300
  return name.split("/").map(canonicalCssIdentPart).filter((part) => part.length > 0);
1226
1301
  }
1227
- function tokenPathToCssVar(path38) {
1228
- return `--${path38.join("-")}`;
1302
+ function tokenPathToCssVar(path40) {
1303
+ return `--${path40.join("-")}`;
1229
1304
  }
1230
1305
  function toDtcgToken(variable, defaultMode) {
1231
1306
  const modes = Object.keys(variable.valuesByMode);
@@ -1269,11 +1344,11 @@ function toDtcgToken(variable, defaultMode) {
1269
1344
  }
1270
1345
  function mapVariablesToDtcg(variables, defaultMode = "light") {
1271
1346
  const entries = variables.map((variable) => {
1272
- const path38 = variableNameToPath(variable.name);
1273
- if (path38.length === 0) {
1347
+ const path40 = variableNameToPath(variable.name);
1348
+ if (path40.length === 0) {
1274
1349
  throw new DtcgMappingError("empty-name", `Figma variable ${variable.id} has an empty name`);
1275
1350
  }
1276
- return { variable, path: path38 };
1351
+ return { variable, path: path40 };
1277
1352
  });
1278
1353
  const groupPrefixes = /* @__PURE__ */ new Set();
1279
1354
  for (const e of entries) {
@@ -1294,21 +1369,21 @@ function mapVariablesToDtcg(variables, defaultMode = "light") {
1294
1369
  }
1295
1370
  const tokens = {};
1296
1371
  const flat = [];
1297
- for (const { variable, path: path38 } of entries) {
1372
+ for (const { variable, path: path40 } of entries) {
1298
1373
  const token = toDtcgToken(variable, defaultMode);
1299
1374
  let group = tokens;
1300
- for (const segment of path38.slice(0, -1)) {
1375
+ for (const segment of path40.slice(0, -1)) {
1301
1376
  const existing = group[segment];
1302
1377
  group = existing ?? (group[segment] = {});
1303
1378
  }
1304
- const leaf = path38[path38.length - 1];
1379
+ const leaf = path40[path40.length - 1];
1305
1380
  if (group[leaf] !== void 0) {
1306
- throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path38.join(".")}" (variable ${variable.id})`);
1381
+ throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path40.join(".")}" (variable ${variable.id})`);
1307
1382
  }
1308
1383
  group[leaf] = token;
1309
1384
  flat.push({
1310
- path: path38.join("."),
1311
- cssVar: tokenPathToCssVar(path38),
1385
+ path: path40.join("."),
1386
+ cssVar: tokenPathToCssVar(path40),
1312
1387
  type: token.$type,
1313
1388
  value: token.$value
1314
1389
  });
@@ -1326,7 +1401,7 @@ function mapVariablesToDtcg(variables, defaultMode = "light") {
1326
1401
  }
1327
1402
  var DIMENSION_COLLECTIONS, DtcgMappingError;
1328
1403
  var init_dtcg = __esm({
1329
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/tokens/src/dtcg.ts"() {
1404
+ "packages/tokens/src/dtcg.ts"() {
1330
1405
  "use strict";
1331
1406
  init_src();
1332
1407
  DIMENSION_COLLECTIONS = /* @__PURE__ */ new Set(["spacing", "radius", "size", "border-width"]);
@@ -1341,7 +1416,7 @@ var init_dtcg = __esm({
1341
1416
  }
1342
1417
  });
1343
1418
 
1344
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/tokens/src/emit.ts
1419
+ // packages/tokens/src/emit.ts
1345
1420
  function decomposeFontValue(raw, valueByCssVar) {
1346
1421
  const field = (name) => new RegExp(`${name}:\\s*("[^"]*"|[^,)]+)`).exec(raw)?.[1]?.trim();
1347
1422
  const resolve = (v) => {
@@ -1408,7 +1483,7 @@ export default {
1408
1483
  }
1409
1484
  var STYLE_WEIGHTS;
1410
1485
  var init_emit = __esm({
1411
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/tokens/src/emit.ts"() {
1486
+ "packages/tokens/src/emit.ts"() {
1412
1487
  "use strict";
1413
1488
  init_src();
1414
1489
  STYLE_WEIGHTS = {
@@ -1427,7 +1502,7 @@ var init_emit = __esm({
1427
1502
  }
1428
1503
  });
1429
1504
 
1430
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/tokens/src/synthesize.ts
1505
+ // packages/tokens/src/synthesize.ts
1431
1506
  function synthesizeSpacingVariables(tree, variantFacts, existing) {
1432
1507
  if (existing.some((v) => v.collection === "spacing")) return [];
1433
1508
  const values = /* @__PURE__ */ new Set();
@@ -1466,7 +1541,7 @@ function synthesizeSpacingVariables(tree, variantFacts, existing) {
1466
1541
  }
1467
1542
  var SPACING_FIELDS;
1468
1543
  var init_synthesize = __esm({
1469
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/tokens/src/synthesize.ts"() {
1544
+ "packages/tokens/src/synthesize.ts"() {
1470
1545
  "use strict";
1471
1546
  SPACING_FIELDS = [
1472
1547
  "paddingTop",
@@ -1478,9 +1553,9 @@ var init_synthesize = __esm({
1478
1553
  }
1479
1554
  });
1480
1555
 
1481
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/tokens/src/index.ts
1556
+ // packages/tokens/src/index.ts
1482
1557
  var init_src2 = __esm({
1483
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/tokens/src/index.ts"() {
1558
+ "packages/tokens/src/index.ts"() {
1484
1559
  "use strict";
1485
1560
  init_dtcg();
1486
1561
  init_emit();
@@ -1489,7 +1564,7 @@ var init_src2 = __esm({
1489
1564
  }
1490
1565
  });
1491
1566
 
1492
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/core/src/ir-builder.ts
1567
+ // packages/core/src/ir-builder.ts
1493
1568
  function isObject(value) {
1494
1569
  return typeof value === "object" && value !== null && !Array.isArray(value);
1495
1570
  }
@@ -1497,9 +1572,9 @@ function boundId(value) {
1497
1572
  return isObject(value) && typeof value["boundVariableId"] === "string" ? value["boundVariableId"] : void 0;
1498
1573
  }
1499
1574
  function resolveBinding(ctx, id) {
1500
- const path38 = ctx.pathById.get(id);
1501
- if (path38 === void 0) ctx.unresolved.add(id);
1502
- return path38;
1575
+ const path40 = ctx.pathById.get(id);
1576
+ if (path40 === void 0) ctx.unresolved.add(id);
1577
+ return path40;
1503
1578
  }
1504
1579
  function parseVariantProps(name) {
1505
1580
  if (!name.includes("=")) return void 0;
@@ -1534,8 +1609,8 @@ function walk(ctx, raw) {
1534
1609
  if (!isObject(paint) || paint["visible"] === false) continue;
1535
1610
  const id = boundId(paint);
1536
1611
  if (id !== void 0) {
1537
- const path38 = resolveBinding(ctx, id);
1538
- if (path38 !== void 0) tokens.add(path38);
1612
+ const path40 = resolveBinding(ctx, id);
1613
+ if (path40 !== void 0) tokens.add(path40);
1539
1614
  } else if (typeof paint["color"] === "string") {
1540
1615
  ctx.hardcoded.push({ node: name, property, value: paint["color"] });
1541
1616
  }
@@ -1543,8 +1618,8 @@ function walk(ctx, raw) {
1543
1618
  }
1544
1619
  const radiusId = boundId(raw["cornerRadius"]);
1545
1620
  if (radiusId !== void 0) {
1546
- const path38 = resolveBinding(ctx, radiusId);
1547
- if (path38 !== void 0) tokens.add(path38);
1621
+ const path40 = resolveBinding(ctx, radiusId);
1622
+ if (path40 !== void 0) tokens.add(path40);
1548
1623
  } else if (typeof raw["cornerRadius"] === "number" && raw["cornerRadius"] !== 0) {
1549
1624
  ctx.hardcoded.push({ node: name, property: "border-radius", value: `${raw["cornerRadius"]}px` });
1550
1625
  }
@@ -1554,10 +1629,10 @@ function walk(ctx, raw) {
1554
1629
  layout = { mode: layoutMode === "HORIZONTAL" ? "flex-row" : "flex-column" };
1555
1630
  const gapId = boundId(raw["itemSpacing"]);
1556
1631
  if (gapId !== void 0) {
1557
- const path38 = resolveBinding(ctx, gapId);
1558
- if (path38 !== void 0) {
1559
- layout.gap = path38;
1560
- tokens.add(path38);
1632
+ const path40 = resolveBinding(ctx, gapId);
1633
+ if (path40 !== void 0) {
1634
+ layout.gap = path40;
1635
+ tokens.add(path40);
1561
1636
  }
1562
1637
  } else if (typeof raw["itemSpacing"] === "number" && raw["itemSpacing"] !== 0) {
1563
1638
  ctx.hardcoded.push({ node: name, property: "gap", value: `${raw["itemSpacing"]}px` });
@@ -1566,10 +1641,10 @@ function walk(ctx, raw) {
1566
1641
  for (const field of PADDING_FIELDS) {
1567
1642
  const id = boundId(raw[field]);
1568
1643
  if (id !== void 0) {
1569
- const path38 = resolveBinding(ctx, id);
1570
- if (path38 !== void 0) {
1571
- paddingPaths.push(path38);
1572
- tokens.add(path38);
1644
+ const path40 = resolveBinding(ctx, id);
1645
+ if (path40 !== void 0) {
1646
+ paddingPaths.push(path40);
1647
+ tokens.add(path40);
1573
1648
  }
1574
1649
  } else if (typeof raw[field] === "number" && raw[field] !== 0) {
1575
1650
  ctx.hardcoded.push({ node: name, property: field, value: `${raw[field]}px` });
@@ -1674,7 +1749,7 @@ function canonicalizeFactIdents(variantFacts) {
1674
1749
  }
1675
1750
  var RAW_PAYLOAD_FLAG_BYTES, MIN_HEALTHY_RATIO, RATIO_CHECK_MIN_BYTES, PADDING_FIELDS;
1676
1751
  var init_ir_builder = __esm({
1677
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/core/src/ir-builder.ts"() {
1752
+ "packages/core/src/ir-builder.ts"() {
1678
1753
  "use strict";
1679
1754
  init_src2();
1680
1755
  init_src2();
@@ -1685,10 +1760,10 @@ var init_ir_builder = __esm({
1685
1760
  }
1686
1761
  });
1687
1762
 
1688
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/core/src/exit-codes.ts
1763
+ // packages/core/src/exit-codes.ts
1689
1764
  var ExitCode;
1690
1765
  var init_exit_codes = __esm({
1691
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/core/src/exit-codes.ts"() {
1766
+ "packages/core/src/exit-codes.ts"() {
1692
1767
  "use strict";
1693
1768
  ExitCode = {
1694
1769
  Success: 0,
@@ -1709,7 +1784,7 @@ var init_exit_codes = __esm({
1709
1784
  }
1710
1785
  });
1711
1786
 
1712
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/core/src/readiness.ts
1787
+ // packages/core/src/readiness.ts
1713
1788
  function collectDefaultNamedLayers(tree, found) {
1714
1789
  const name = tree["name"];
1715
1790
  const visible = tree["visible"];
@@ -1759,15 +1834,15 @@ function scoreReadiness(metadata, tree) {
1759
1834
  }
1760
1835
  var DEFAULT_LAYER_NAME;
1761
1836
  var init_readiness = __esm({
1762
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/core/src/readiness.ts"() {
1837
+ "packages/core/src/readiness.ts"() {
1763
1838
  "use strict";
1764
1839
  DEFAULT_LAYER_NAME = /^(Group|Frame|Rectangle|Ellipse|Vector|Component)\s*\d*$/;
1765
1840
  }
1766
1841
  });
1767
1842
 
1768
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/core/src/index.ts
1843
+ // packages/core/src/index.ts
1769
1844
  var init_src3 = __esm({
1770
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/core/src/index.ts"() {
1845
+ "packages/core/src/index.ts"() {
1771
1846
  "use strict";
1772
1847
  init_ir();
1773
1848
  init_ir_builder();
@@ -1776,20 +1851,72 @@ var init_src3 = __esm({
1776
1851
  }
1777
1852
  });
1778
1853
 
1779
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/browser.ts
1780
- import { execFileSync } from "node:child_process";
1781
- import { existsSync as existsSync3, readdirSync } from "node:fs";
1854
+ // packages/cli/src/invocation.ts
1855
+ import { existsSync as existsSync3, realpathSync } from "node:fs";
1782
1856
  import path3 from "node:path";
1857
+ import { fileURLToPath } from "node:url";
1858
+ function findPathTendril(pathEnv, platform) {
1859
+ const dirs = pathEnv.split(path3.delimiter).filter((d) => d !== "" && !/node_modules[\\/]\.bin/.test(d) && !/[\\/]_npx[\\/]/.test(d));
1860
+ const names = platform === "win32" ? ["tendril.cmd", "tendril.bat"] : ["tendril"];
1861
+ for (const dir of dirs) {
1862
+ for (const name of names) {
1863
+ const candidate = path3.join(dir, name);
1864
+ if (existsSync3(candidate)) return candidate;
1865
+ }
1866
+ }
1867
+ return null;
1868
+ }
1869
+ function packageRootOf(file) {
1870
+ let dir = path3.dirname(file);
1871
+ for (; ; ) {
1872
+ if (existsSync3(path3.join(dir, "package.json"))) return dir;
1873
+ const parent = path3.dirname(dir);
1874
+ if (parent === dir) return null;
1875
+ dir = parent;
1876
+ }
1877
+ }
1878
+ function resolveInvocation(pathEnv, platform, selfFile = fileURLToPath(import.meta.url)) {
1879
+ const binary = findPathTendril(pathEnv, platform);
1880
+ if (binary === null) return NPX_INVOCATION;
1881
+ try {
1882
+ const onPath = packageRootOf(realpathSync(binary));
1883
+ const self = packageRootOf(realpathSync(selfFile));
1884
+ if (onPath === null || self === null) return NPX_INVOCATION;
1885
+ return realpathSync(onPath) === realpathSync(self) ? "tendril" : NPX_INVOCATION;
1886
+ } catch {
1887
+ return NPX_INVOCATION;
1888
+ }
1889
+ }
1890
+ function tendrilInvocation() {
1891
+ cached ??= resolveInvocation(process.env["PATH"] ?? "", process.platform);
1892
+ return cached;
1893
+ }
1894
+ function tendrilCommand(args) {
1895
+ return `${tendrilInvocation()} ${args}`;
1896
+ }
1897
+ var NPX_INVOCATION, cached;
1898
+ var init_invocation = __esm({
1899
+ "packages/cli/src/invocation.ts"() {
1900
+ "use strict";
1901
+ NPX_INVOCATION = "npx -y -p @tendrilapp/cli@latest tendril";
1902
+ cached = null;
1903
+ }
1904
+ });
1905
+
1906
+ // packages/verify/src/browser.ts
1907
+ import { execFileSync } from "node:child_process";
1908
+ import { existsSync as existsSync4, readdirSync } from "node:fs";
1909
+ import path4 from "node:path";
1783
1910
  function resolveChrome() {
1784
1911
  const fromEnv = process.env["TENDRIL_CHROME"] ?? process.env["CHROME_PATH"];
1785
1912
  if (fromEnv !== void 0 && fromEnv !== "") {
1786
- if (!existsSync3(fromEnv)) {
1913
+ if (!existsSync4(fromEnv)) {
1787
1914
  throw new Error(`CHROME_PATH points at ${fromEnv}, which does not exist \u2014 fix the variable or unset it to use discovery`);
1788
1915
  }
1789
1916
  return fromEnv;
1790
1917
  }
1791
1918
  const candidates = process.platform === "darwin" ? MAC_CANDIDATES : process.platform === "win32" ? WIN_CANDIDATES : [];
1792
- for (const c of candidates) if (existsSync3(c)) return c;
1919
+ for (const c of candidates) if (existsSync4(c)) return c;
1793
1920
  if (process.platform !== "win32") {
1794
1921
  for (const name of PATH_NAMES) {
1795
1922
  try {
@@ -1803,7 +1930,7 @@ function resolveChrome() {
1803
1930
  }
1804
1931
  function versionFromInstallDir(exePath) {
1805
1932
  try {
1806
- const builds = readdirSync(path3.dirname(exePath), { withFileTypes: true }).filter((e) => e.isDirectory() && /^\d+(\.\d+){3}$/.test(e.name)).map((e) => e.name.split(".").map(Number));
1933
+ const builds = readdirSync(path4.dirname(exePath), { withFileTypes: true }).filter((e) => e.isDirectory() && /^\d+(\.\d+){3}$/.test(e.name)).map((e) => e.name.split(".").map(Number));
1807
1934
  builds.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2] || a[3] - b[3]);
1808
1935
  return builds.length === 0 ? null : builds[builds.length - 1].join(".");
1809
1936
  } catch {
@@ -1815,7 +1942,7 @@ function windowsBrowserName(exePath) {
1815
1942
  if (p.includes("\\google\\chrome\\")) return "Google Chrome";
1816
1943
  if (p.includes("\\microsoft\\edge\\")) return "Microsoft Edge";
1817
1944
  if (p.includes("chromium")) return "Chromium";
1818
- return path3.win32.basename(exePath, ".exe");
1945
+ return path4.win32.basename(exePath, ".exe");
1819
1946
  }
1820
1947
  function chromeVersion() {
1821
1948
  if (_version !== void 0) return _version;
@@ -1834,7 +1961,7 @@ function chromeVersion() {
1834
1961
  }
1835
1962
  var MAC_CANDIDATES, WIN_CANDIDATES, PATH_NAMES, _version;
1836
1963
  var init_browser = __esm({
1837
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/browser.ts"() {
1964
+ "packages/verify/src/browser.ts"() {
1838
1965
  "use strict";
1839
1966
  MAC_CANDIDATES = [
1840
1967
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
@@ -1849,33 +1976,33 @@ var init_browser = __esm({
1849
1976
  }
1850
1977
  });
1851
1978
 
1852
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/runtime.ts
1853
- import { existsSync as existsSync4, mkdtempSync, symlinkSync } from "node:fs";
1979
+ // packages/verify/src/runtime.ts
1980
+ import { existsSync as existsSync5, mkdtempSync, symlinkSync } from "node:fs";
1854
1981
  import { createRequire } from "node:module";
1855
1982
  import os from "node:os";
1856
- import path4 from "node:path";
1857
- import { fileURLToPath } from "node:url";
1983
+ import path5 from "node:path";
1984
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
1858
1985
  function runtimePackageRoot() {
1859
1986
  const env = process.env["TENDRIL_PACKAGE_ROOT"];
1860
- if (env !== void 0 && env !== "") return path4.resolve(env);
1861
- return path4.resolve(path4.dirname(fileURLToPath(import.meta.url)), "..");
1987
+ if (env !== void 0 && env !== "") return path5.resolve(env);
1988
+ return path5.resolve(path5.dirname(fileURLToPath2(import.meta.url)), "..");
1862
1989
  }
1863
1990
  function runtimeNodeModules() {
1864
1991
  const root = runtimePackageRoot();
1865
- for (let dir = root; ; dir = path4.dirname(dir)) {
1866
- const candidate = path4.basename(dir) === "node_modules" ? dir : path4.join(dir, "node_modules");
1867
- if (existsSync4(path4.join(candidate, "react"))) return candidate;
1868
- if (path4.dirname(dir) === dir) break;
1992
+ for (let dir = root; ; dir = path5.dirname(dir)) {
1993
+ const candidate = path5.basename(dir) === "node_modules" ? dir : path5.join(dir, "node_modules");
1994
+ if (existsSync5(path5.join(candidate, "react"))) return candidate;
1995
+ if (path5.dirname(dir) === dir) break;
1869
1996
  }
1870
1997
  throw new Error(
1871
1998
  `Tendril's installed dependencies are missing: no node_modules containing react found at or above ${root}. This is an INSTALLATION problem, not a component error \u2014 reinstall with \`npm install -g @tendrilapp/cli\` (or \`tendrilapp\`) and retry.`
1872
1999
  );
1873
2000
  }
1874
2001
  function newScratchDir(prefix) {
1875
- const dir = mkdtempSync(path4.join(os.tmpdir(), `tendril-${prefix}-`));
2002
+ const dir = mkdtempSync(path5.join(os.tmpdir(), `tendril-${prefix}-`));
1876
2003
  const nodeModules = runtimeNodeModules();
1877
2004
  try {
1878
- symlinkSync(nodeModules, path4.join(dir, "node_modules"), "junction");
2005
+ symlinkSync(nodeModules, path5.join(dir, "node_modules"), "junction");
1879
2006
  } catch (err) {
1880
2007
  throw new Error(
1881
2008
  `Tendril could not link its dependencies into the scratch dir (${nodeModules} -> ${dir}): ${err instanceof Error ? err.message : String(err)}. This is an environment problem, not a component error.`
@@ -1884,7 +2011,7 @@ function newScratchDir(prefix) {
1884
2011
  return dir;
1885
2012
  }
1886
2013
  function reactPinPlugin() {
1887
- const req = createRequire(path4.join(runtimePackageRoot(), "package.json"));
2014
+ const req = createRequire(path5.join(runtimePackageRoot(), "package.json"));
1888
2015
  return {
1889
2016
  name: "tendril-react-pin",
1890
2017
  setup(b) {
@@ -1899,12 +2026,12 @@ function reactPinPlugin() {
1899
2026
  };
1900
2027
  }
1901
2028
  var init_runtime = __esm({
1902
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/runtime.ts"() {
2029
+ "packages/verify/src/runtime.ts"() {
1903
2030
  "use strict";
1904
2031
  }
1905
2032
  });
1906
2033
 
1907
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/gates.ts
2034
+ // packages/verify/src/gates.ts
1908
2035
  function scanForbiddenPatterns(source) {
1909
2036
  const violations = [];
1910
2037
  const lines = source.split("\n");
@@ -1923,7 +2050,7 @@ function scanForbiddenPatterns(source) {
1923
2050
  }
1924
2051
  var FORBIDDEN_PATTERNS;
1925
2052
  var init_gates = __esm({
1926
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/gates.ts"() {
2053
+ "packages/verify/src/gates.ts"() {
1927
2054
  "use strict";
1928
2055
  FORBIDDEN_PATTERNS = [
1929
2056
  { name: "eslint-disable", regex: /eslint-disable/ },
@@ -1935,11 +2062,11 @@ var init_gates = __esm({
1935
2062
  }
1936
2063
  });
1937
2064
 
1938
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/tsc-check.ts
2065
+ // packages/verify/src/tsc-check.ts
1939
2066
  import ts from "typescript";
1940
- import path5 from "node:path";
2067
+ import path6 from "node:path";
1941
2068
  function runTscStrict(files) {
1942
- const program = ts.createProgram(files.map((f) => path5.resolve(f)), STRICT_OPTIONS);
2069
+ const program = ts.createProgram(files.map((f) => path6.resolve(f)), STRICT_OPTIONS);
1943
2070
  const diagnostics = ts.getPreEmitDiagnostics(program);
1944
2071
  const mapped = diagnostics.map((d) => {
1945
2072
  const file = d.file?.fileName;
@@ -1955,7 +2082,7 @@ function runTscStrict(files) {
1955
2082
  }
1956
2083
  var STRICT_OPTIONS;
1957
2084
  var init_tsc_check = __esm({
1958
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/tsc-check.ts"() {
2085
+ "packages/verify/src/tsc-check.ts"() {
1959
2086
  "use strict";
1960
2087
  STRICT_OPTIONS = {
1961
2088
  strict: true,
@@ -1975,7 +2102,7 @@ var init_tsc_check = __esm({
1975
2102
  }
1976
2103
  });
1977
2104
 
1978
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/token-lint.ts
2105
+ // packages/verify/src/token-lint.ts
1979
2106
  import strictValue from "stylelint-declaration-strict-value";
1980
2107
  import stylelint from "stylelint";
1981
2108
  async function runTokenLint(css, fileLabel = "generated.css", definedVars2, recordedMapEmpty = false) {
@@ -2036,7 +2163,7 @@ async function runTokenLint(css, fileLabel = "generated.css", definedVars2, reco
2036
2163
  }
2037
2164
  var TOKEN_ENFORCED_PROPERTIES, ALLOWED_KEYWORDS, STYLELINT_CONFIG, LEGAL_CUSTOM_PROP;
2038
2165
  var init_token_lint = __esm({
2039
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/token-lint.ts"() {
2166
+ "packages/verify/src/token-lint.ts"() {
2040
2167
  "use strict";
2041
2168
  TOKEN_ENFORCED_PROPERTIES = [
2042
2169
  "/color$/",
@@ -2096,9 +2223,9 @@ var init_token_lint = __esm({
2096
2223
  }
2097
2224
  });
2098
2225
 
2099
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/loop.ts
2226
+ // packages/verify/src/loop.ts
2100
2227
  import { rmSync, writeFileSync as writeFileSync2 } from "node:fs";
2101
- import path6 from "node:path";
2228
+ import path7 from "node:path";
2102
2229
  async function runChecks(componentName, files, extraFiles, definedVars2) {
2103
2230
  const findings = [];
2104
2231
  for (const violation of scanForbiddenPatterns(files.tsx)) {
@@ -2113,10 +2240,10 @@ async function runChecks(componentName, files, extraFiles, definedVars2) {
2113
2240
  }
2114
2241
  const workDir = newScratchDir("loop");
2115
2242
  try {
2116
- const tsxPath = path6.join(workDir, `${componentName}.tsx`);
2243
+ const tsxPath = path7.join(workDir, `${componentName}.tsx`);
2117
2244
  writeFileSync2(tsxPath, files.tsx);
2118
2245
  for (const [name, content] of Object.entries(extraFiles ?? {})) {
2119
- writeFileSync2(path6.join(workDir, name), content);
2246
+ writeFileSync2(path7.join(workDir, name), content);
2120
2247
  }
2121
2248
  const tsc = runTscStrict([tsxPath]);
2122
2249
  for (const d of tsc.diagnostics) {
@@ -2170,7 +2297,7 @@ function definedVarsFrom(tokensCss) {
2170
2297
  }
2171
2298
  var SKIPPED_CHECKS;
2172
2299
  var init_loop = __esm({
2173
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/loop.ts"() {
2300
+ "packages/verify/src/loop.ts"() {
2174
2301
  "use strict";
2175
2302
  init_runtime();
2176
2303
  init_gates();
@@ -2180,7 +2307,7 @@ var init_loop = __esm({
2180
2307
  }
2181
2308
  });
2182
2309
 
2183
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/report.ts
2310
+ // packages/verify/src/report.ts
2184
2311
  import { z as z5 } from "zod";
2185
2312
  function isTokenEnforcedProperty(property) {
2186
2313
  return enforcedMatchers.some((m) => m.test(property));
@@ -2220,7 +2347,7 @@ function buildReport(input) {
2220
2347
  }
2221
2348
  var ReportSchema, enforcedMatchers;
2222
2349
  var init_report = __esm({
2223
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/report.ts"() {
2350
+ "packages/verify/src/report.ts"() {
2224
2351
  "use strict";
2225
2352
  init_token_lint();
2226
2353
  ReportSchema = z5.object({
@@ -2282,7 +2409,7 @@ var init_report = __esm({
2282
2409
  }
2283
2410
  });
2284
2411
 
2285
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/mount-limits.ts
2412
+ // packages/verify/src/mount-limits.ts
2286
2413
  function lcdTextEnabled() {
2287
2414
  return process.env["TENDRIL_ENABLE_LCD_TEXT"] === "1";
2288
2415
  }
@@ -2300,14 +2427,14 @@ function raceMountDeadline(work, deadlineMs, onDeadline) {
2300
2427
  }
2301
2428
  var MEMORY_CAP_ARG, ENGINE_MOUNT_DEADLINE_MS;
2302
2429
  var init_mount_limits = __esm({
2303
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/mount-limits.ts"() {
2430
+ "packages/verify/src/mount-limits.ts"() {
2304
2431
  "use strict";
2305
2432
  MEMORY_CAP_ARG = "--js-flags=--max-old-space-size=512";
2306
2433
  ENGINE_MOUNT_DEADLINE_MS = 3e4;
2307
2434
  }
2308
2435
  });
2309
2436
 
2310
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/image-diff.ts
2437
+ // packages/verify/src/image-diff.ts
2311
2438
  import { PNG } from "pngjs";
2312
2439
  import pixelmatch from "pixelmatch";
2313
2440
  function channelDistance(a, b) {
@@ -2749,7 +2876,7 @@ function cropPng(png, x, y, width, height) {
2749
2876
  }
2750
2877
  var INK_DELTA, ABSENT_RADIUS, ABSENT_MIN_PX;
2751
2878
  var init_image_diff = __esm({
2752
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/image-diff.ts"() {
2879
+ "packages/verify/src/image-diff.ts"() {
2753
2880
  "use strict";
2754
2881
  INK_DELTA = 30;
2755
2882
  ABSENT_RADIUS = 3;
@@ -2757,9 +2884,9 @@ var init_image_diff = __esm({
2757
2884
  }
2758
2885
  });
2759
2886
 
2760
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/visual-facts.ts
2761
- import path7 from "node:path";
2762
- import { fileURLToPath as fileURLToPath2 } from "node:url";
2887
+ // packages/verify/src/visual-facts.ts
2888
+ import path8 from "node:path";
2889
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
2763
2890
  import { build } from "esbuild";
2764
2891
  import { chromium } from "playwright-core";
2765
2892
  function resolveAxisValue(declared, props, axis, value) {
@@ -3298,12 +3425,12 @@ ${input.files.css}
3298
3425
  }
3299
3426
  var RESOLVE_DIR, TOLERANCE_PX, WIDTH_SLACK, IMAGE_SIMILARITY_FLOOR, INK_RECALL_FLOOR;
3300
3427
  var init_visual_facts = __esm({
3301
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/visual-facts.ts"() {
3428
+ "packages/verify/src/visual-facts.ts"() {
3302
3429
  "use strict";
3303
3430
  init_browser();
3304
3431
  init_mount_limits();
3305
3432
  init_image_diff();
3306
- RESOLVE_DIR = path7.resolve(path7.dirname(fileURLToPath2(import.meta.url)), "..");
3433
+ RESOLVE_DIR = path8.resolve(path8.dirname(fileURLToPath3(import.meta.url)), "..");
3307
3434
  TOLERANCE_PX = 2;
3308
3435
  WIDTH_SLACK = 0.25;
3309
3436
  IMAGE_SIMILARITY_FLOOR = 0.8;
@@ -3311,27 +3438,27 @@ var init_visual_facts = __esm({
3311
3438
  }
3312
3439
  });
3313
3440
 
3314
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/paths.ts
3315
- import path8 from "node:path";
3316
- import { fileURLToPath as fileURLToPath3 } from "node:url";
3441
+ // packages/verify/src/paths.ts
3442
+ import path9 from "node:path";
3443
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
3317
3444
  var VERIFY_PKG_DIR, REPO_ROOT;
3318
3445
  var init_paths = __esm({
3319
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/paths.ts"() {
3446
+ "packages/verify/src/paths.ts"() {
3320
3447
  "use strict";
3321
- VERIFY_PKG_DIR = path8.resolve(path8.dirname(fileURLToPath3(import.meta.url)), "..");
3322
- REPO_ROOT = path8.resolve(VERIFY_PKG_DIR, "..", "..");
3448
+ VERIFY_PKG_DIR = path9.resolve(path9.dirname(fileURLToPath4(import.meta.url)), "..");
3449
+ REPO_ROOT = path9.resolve(VERIFY_PKG_DIR, "..", "..");
3323
3450
  }
3324
3451
  });
3325
3452
 
3326
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/font-resolve.ts
3453
+ // packages/verify/src/font-resolve.ts
3327
3454
  import { createHash } from "node:crypto";
3328
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
3455
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
3329
3456
  import os2 from "node:os";
3330
- import path9 from "node:path";
3457
+ import path10 from "node:path";
3331
3458
  function fontCacheDir() {
3332
3459
  const env = process.env["TENDRIL_FONT_CACHE"];
3333
- if (env !== void 0 && env !== "") return path9.resolve(env);
3334
- return path9.join(os2.homedir(), ".tendril", "fonts");
3460
+ if (env !== void 0 && env !== "") return path10.resolve(env);
3461
+ return path10.join(os2.homedir(), ".tendril", "fonts");
3335
3462
  }
3336
3463
  function normalizeFontLicense(value) {
3337
3464
  return typeof value === "string" && FONT_LICENSES.includes(value) ? value : "unknown";
@@ -3396,25 +3523,25 @@ async function resolveFonts(family, weights, cacheDir = DEFAULT_FONT_CACHE) {
3396
3523
  }
3397
3524
  const bytes = new Uint8Array(await fileRes.arrayBuffer());
3398
3525
  const sha256 = createHash("sha256").update(bytes).digest("hex");
3399
- const file = path9.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}.woff2`);
3526
+ const file = path10.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}.woff2`);
3400
3527
  writeFileSync3(file, bytes);
3401
3528
  resolved.push({ family, weight, source: url, sha256, file, license: meta.license, ...meta.family !== null ? { servedFamily: meta.family } : {} });
3402
3529
  } catch (err) {
3403
3530
  failures.push({ family, weight, reason: `download failed: ${err instanceof Error ? err.message : String(err)}` });
3404
3531
  }
3405
3532
  }
3406
- const mPath = path9.join(cacheDir, "manifest.json");
3407
- const prior = existsSync5(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
3408
- const portable2 = resolved.map((m) => ({ ...m, file: path9.basename(m.file) }));
3533
+ const mPath = path10.join(cacheDir, "manifest.json");
3534
+ const prior = existsSync6(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
3535
+ const portable2 = resolved.map((m) => ({ ...m, file: path10.basename(m.file) }));
3409
3536
  const merged = [...prior.filter((p) => !resolved.some((m) => m.family === p.family && m.weight === p.weight)), ...portable2];
3410
3537
  if (resolved.length > 0) writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
3411
3538
  `);
3412
3539
  return { resolved, failures };
3413
3540
  }
3414
3541
  function addLocalFont(family, weight, filePath, cacheDir = DEFAULT_FONT_CACHE) {
3415
- const src = path9.resolve(filePath);
3416
- if (!existsSync5(src)) throw new Error(`font file not found: ${src}`);
3417
- const ext = path9.extname(src).toLowerCase();
3542
+ const src = path10.resolve(filePath);
3543
+ if (!existsSync6(src)) throw new Error(`font file not found: ${src}`);
3544
+ const ext = path10.extname(src).toLowerCase();
3418
3545
  if (![".woff2", ".woff", ".ttf", ".otf"].includes(ext)) {
3419
3546
  throw new Error(`unsupported font file "${ext}" \u2014 use .woff2 (preferred), .woff, .ttf or .otf`);
3420
3547
  }
@@ -3422,20 +3549,20 @@ function addLocalFont(family, weight, filePath, cacheDir = DEFAULT_FONT_CACHE) {
3422
3549
  if (bytes.length === 0) throw new Error(`font file is empty: ${src}`);
3423
3550
  mkdirSync2(cacheDir, { recursive: true });
3424
3551
  const sha256 = createHash("sha256").update(bytes).digest("hex");
3425
- const file = path9.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}${ext}`);
3552
+ const file = path10.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}${ext}`);
3426
3553
  writeFileSync3(file, bytes);
3427
- const face = { family, weight, source: `local:${path9.basename(src)}`, sha256, file, license: "unknown" };
3428
- const mPath = path9.join(cacheDir, "manifest.json");
3429
- const prior = existsSync5(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
3430
- const merged = [...prior.filter((p) => !(p.family === family && p.weight === weight)), { ...face, file: path9.basename(file) }];
3554
+ const face = { family, weight, source: `local:${path10.basename(src)}`, sha256, file, license: "unknown" };
3555
+ const mPath = path10.join(cacheDir, "manifest.json");
3556
+ const prior = existsSync6(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
3557
+ const merged = [...prior.filter((p) => !(p.family === family && p.weight === weight)), { ...face, file: path10.basename(file) }];
3431
3558
  writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
3432
3559
  `);
3433
3560
  return face;
3434
3561
  }
3435
3562
  function checkFontLock(lockPath, cacheDir = DEFAULT_FONT_CACHE) {
3436
3563
  const lock = JSON.parse(readFileSync3(lockPath, "utf8"));
3437
- const mPath = path9.join(cacheDir, "manifest.json");
3438
- const manifest = existsSync5(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
3564
+ const mPath = path10.join(cacheDir, "manifest.json");
3565
+ const manifest = existsSync6(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
3439
3566
  return lock.map((l) => {
3440
3567
  const m = manifest.find((x) => x.family === l.family && x.weight === l.weight);
3441
3568
  if (m === void 0) return { family: l.family, weight: l.weight, status: "missing" };
@@ -3443,8 +3570,8 @@ function checkFontLock(lockPath, cacheDir = DEFAULT_FONT_CACHE) {
3443
3570
  });
3444
3571
  }
3445
3572
  function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
3446
- const mPath = path9.join(cacheDir, "manifest.json");
3447
- if (!existsSync5(mPath)) return [];
3573
+ const mPath = path10.join(cacheDir, "manifest.json");
3574
+ if (!existsSync6(mPath)) return [];
3448
3575
  let entries;
3449
3576
  try {
3450
3577
  entries = JSON.parse(readFileSync3(mPath, "utf8"));
@@ -3461,8 +3588,8 @@ function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
3461
3588
  return [...byFamily.entries()].map(([family, w]) => ({ family, weights: [...w].sort((a, b) => a - b) }));
3462
3589
  }
3463
3590
  function requiredFontsManifest(families, cacheDir = DEFAULT_FONT_CACHE) {
3464
- const mPath = path9.join(cacheDir, "manifest.json");
3465
- const manifest = existsSync5(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
3591
+ const mPath = path10.join(cacheDir, "manifest.json");
3592
+ const manifest = existsSync6(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
3466
3593
  const wanted = new Set(families.map((f) => f.toLowerCase()));
3467
3594
  return manifest.filter((f) => wanted.has(f.family.toLowerCase())).map((f) => ({
3468
3595
  family: f.family,
@@ -3475,8 +3602,8 @@ function requiredFontsManifest(families, cacheDir = DEFAULT_FONT_CACHE) {
3475
3602
  }));
3476
3603
  }
3477
3604
  function facesMissingLicense(cacheDir = DEFAULT_FONT_CACHE) {
3478
- const mPath = path9.join(cacheDir, "manifest.json");
3479
- if (!existsSync5(mPath)) return [];
3605
+ const mPath = path10.join(cacheDir, "manifest.json");
3606
+ if (!existsSync6(mPath)) return [];
3480
3607
  let entries;
3481
3608
  try {
3482
3609
  entries = JSON.parse(readFileSync3(mPath, "utf8"));
@@ -3488,8 +3615,8 @@ function facesMissingLicense(cacheDir = DEFAULT_FONT_CACHE) {
3488
3615
  ).map((e) => ({ family: e.family, weight: e.weight, sha256: e.sha256 }));
3489
3616
  }
3490
3617
  function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
3491
- const mPath = path9.join(cacheDir, "manifest.json");
3492
- if (!existsSync5(mPath)) return [];
3618
+ const mPath = path10.join(cacheDir, "manifest.json");
3619
+ if (!existsSync6(mPath)) return [];
3493
3620
  let entries;
3494
3621
  try {
3495
3622
  entries = JSON.parse(readFileSync3(mPath, "utf8"));
@@ -3499,8 +3626,8 @@ function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
3499
3626
  const byFamily = /* @__PURE__ */ new Map();
3500
3627
  for (const e of entries) {
3501
3628
  if (typeof e.family !== "string" || typeof e.weight !== "number" || typeof e.file !== "string") continue;
3502
- const file = path9.isAbsolute(e.file) && existsSync5(e.file) ? e.file : path9.resolve(cacheDir, path9.basename(e.file));
3503
- if (!existsSync5(file)) continue;
3629
+ const file = path10.isAbsolute(e.file) && existsSync6(e.file) ? e.file : path10.resolve(cacheDir, path10.basename(e.file));
3630
+ if (!existsSync6(file)) continue;
3504
3631
  if (createHash("sha256").update(readFileSync3(file)).digest("hex") !== e.sha256) continue;
3505
3632
  const set = byFamily.get(e.family) ?? /* @__PURE__ */ new Set();
3506
3633
  set.add(e.weight);
@@ -3510,7 +3637,7 @@ function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
3510
3637
  }
3511
3638
  var DEFAULT_FONT_CACHE, UA, FONT_LICENSES, GOOGLE_LICENSE_IDS, GOOGLE_FONT_FILE_PREFIX;
3512
3639
  var init_font_resolve = __esm({
3513
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/font-resolve.ts"() {
3640
+ "packages/verify/src/font-resolve.ts"() {
3514
3641
  "use strict";
3515
3642
  DEFAULT_FONT_CACHE = fontCacheDir();
3516
3643
  UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36";
@@ -3525,65 +3652,76 @@ var init_font_resolve = __esm({
3525
3652
  }
3526
3653
  });
3527
3654
 
3528
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/font-faces.ts
3655
+ // packages/verify/src/font-faces.ts
3529
3656
  import { createHash as createHash2 } from "node:crypto";
3530
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
3531
- import path10 from "node:path";
3532
- function fontFaceCss(manifestPath2 = path10.join(fontCacheDir(), "manifest.json")) {
3533
- if (!existsSync6(manifestPath2)) return "";
3657
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
3658
+ import path11 from "node:path";
3659
+ function injectedGroups(manifestPath2) {
3660
+ if (!existsSync7(manifestPath2)) return { groups: [], shared: false };
3534
3661
  const claimed = JSON.parse(readFileSync4(manifestPath2, "utf8"));
3535
- const resolveEntry = (f) => {
3536
- if (path10.isAbsolute(f) && existsSync6(f)) return f;
3537
- return path10.resolve(path10.dirname(manifestPath2), path10.basename(f));
3538
- };
3539
- const manifest = claimed.filter((f) => {
3540
- const file = resolveEntry(f.file);
3541
- if (!existsSync6(file)) return false;
3542
- return createHash2("sha256").update(readFileSync4(file)).digest("hex") === f.sha256;
3543
- });
3544
- const resolveFile = (f) => {
3545
- if (path10.isAbsolute(f) && existsSync6(f)) return f;
3546
- return path10.resolve(path10.dirname(manifestPath2), path10.basename(f));
3547
- };
3662
+ const resolveFile = (f) => path11.isAbsolute(f) && existsSync7(f) ? f : path11.resolve(path11.dirname(manifestPath2), path11.basename(f));
3548
3663
  const byFile = /* @__PURE__ */ new Map();
3549
- for (const f of manifest) {
3664
+ for (const f of claimed) {
3665
+ const file = resolveFile(f.file);
3666
+ if (!existsSync7(file)) continue;
3667
+ if (createHash2("sha256").update(readFileSync4(file)).digest("hex") !== f.sha256) continue;
3550
3668
  const k = `${f.family}:${f.file}`;
3551
- const e = byFile.get(k) ?? { family: f.family, weights: [], file: resolveFile(f.file) };
3669
+ const e = byFile.get(k) ?? { family: f.family, weights: [], file };
3552
3670
  e.weights.push(f.weight);
3553
3671
  byFile.set(k, e);
3554
3672
  }
3555
- const files = new Set([...byFile.values()].map((e) => e.file));
3556
- const shared = files.size < byFile.size;
3557
- return [...byFile.values()].map((e) => {
3558
- const weight = shared || e.weights.length > 1 ? "100 700" : String(e.weights[0]);
3673
+ const groups = [...byFile.values()];
3674
+ return { groups, shared: new Set(groups.map((e) => e.file)).size < groups.length };
3675
+ }
3676
+ function fontFaceCss(manifestPath2 = path11.join(fontCacheDir(), "manifest.json")) {
3677
+ const { groups, shared } = injectedGroups(manifestPath2);
3678
+ return groups.map((e) => {
3679
+ const weight = shared || e.weights.length > 1 ? `${SPAN[0]} ${SPAN[1]}` : String(e.weights[0]);
3559
3680
  return `@font-face { font-family: '${e.family}'; font-weight: ${weight}; font-style: normal; src: url(data:font/woff2;base64,${readFileSync4(e.file).toString("base64")}) format('woff2'); }`;
3560
3681
  }).join("\n");
3561
3682
  }
3683
+ function injectedFamilyWeights(manifestPath2 = path11.join(fontCacheDir(), "manifest.json")) {
3684
+ const { groups, shared } = injectedGroups(manifestPath2);
3685
+ const out = /* @__PURE__ */ new Map();
3686
+ for (const g of groups) {
3687
+ const key = g.family.toLowerCase();
3688
+ const e = out.get(key) ?? { weights: /* @__PURE__ */ new Set(), span: false };
3689
+ for (const w of g.weights) e.weights.add(w);
3690
+ if (shared || g.weights.length > 1) e.span = true;
3691
+ out.set(key, e);
3692
+ }
3693
+ return out;
3694
+ }
3695
+ function servesWeight(coverage, weight) {
3696
+ return coverage.weights.has(weight) || coverage.span && weight >= SPAN[0] && weight <= SPAN[1];
3697
+ }
3698
+ var SPAN;
3562
3699
  var init_font_faces = __esm({
3563
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/font-faces.ts"() {
3700
+ "packages/verify/src/font-faces.ts"() {
3564
3701
  "use strict";
3565
3702
  init_font_resolve();
3703
+ SPAN = [100, 700];
3566
3704
  }
3567
3705
  });
3568
3706
 
3569
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/admission.ts
3570
- import { readFileSync as readFileSync5, readdirSync as readdirSync2, existsSync as existsSync7, writeFileSync as writeFileSync4 } from "node:fs";
3571
- import path11 from "node:path";
3707
+ // packages/verify/src/admission.ts
3708
+ import { readFileSync as readFileSync5, readdirSync as readdirSync2, existsSync as existsSync8, writeFileSync as writeFileSync4 } from "node:fs";
3709
+ import path12 from "node:path";
3572
3710
  import { build as build2 } from "esbuild";
3573
3711
  import postcss from "postcss";
3574
3712
  import tailwindcss from "tailwindcss";
3575
3713
  import { chromium as chromium2 } from "playwright-core";
3576
3714
  function fontWeightsByFamily() {
3577
- const mPath = path11.join(fontCacheDir(), "manifest.json");
3715
+ const mPath = path12.join(fontCacheDir(), "manifest.json");
3578
3716
  const out = /* @__PURE__ */ new Map();
3579
- if (!existsSync7(mPath)) return out;
3717
+ if (!existsSync8(mPath)) return out;
3580
3718
  for (const f of JSON.parse(readFileSync5(mPath, "utf8")))
3581
3719
  out.set(f.family, [...out.get(f.family) ?? [], f.weight]);
3582
3720
  return out;
3583
3721
  }
3584
3722
  var FONT_WEIGHTS;
3585
3723
  var init_admission = __esm({
3586
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/admission.ts"() {
3724
+ "packages/verify/src/admission.ts"() {
3587
3725
  "use strict";
3588
3726
  init_paths();
3589
3727
  init_src();
@@ -3596,11 +3734,11 @@ var init_admission = __esm({
3596
3734
  }
3597
3735
  });
3598
3736
 
3599
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/tasks.ts
3600
- import path12 from "node:path";
3737
+ // packages/verify/src/tasks.ts
3738
+ import path13 from "node:path";
3601
3739
  var CALENDAR_CONFIGS, CALENDAR_API, CALENDAR_BEHAVIORS, BUTTON_CONFIGS, BUTTON_API, COMBO_FIX, COMBO_CONFIGS, COMBO_API, MODAL_CONFIGS, MODAL_API, BUTTON_BEHAVIORS, COMBO_BEHAVIORS, MODAL_BEHAVIORS, TASKS;
3602
3740
  var init_tasks = __esm({
3603
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/tasks.ts"() {
3741
+ "packages/verify/src/tasks.ts"() {
3604
3742
  "use strict";
3605
3743
  init_paths();
3606
3744
  CALENDAR_CONFIGS = [
@@ -3677,7 +3815,7 @@ export function Button(props: {
3677
3815
  [key: string]: unknown; // MUST spread unknown props (incl. data-*) onto the root element
3678
3816
  })
3679
3817
 
3680
- Rules: plain CSS in styles.css (tokens.css optional, loaded first) \u2014 no Tailwind, no imports beyond react/react-dom. disabled buttons are unfocusable; the spinner honors prefers-reduced-motion (animation: none under the media query). Interactive states are REAL (:hover, :focus-visible) AND statically forceable via the data-tendril-state attribute ("hover" | "focus-visible") spread onto the root; forced and real selectors must share one declaration block, e.g. :is(:hover, [data-tendril-state~="hover"]). The host sizes nothing: the button is its natural recorded size. Fonts: 'Inter' is provided. Inline all SVGs (loading spinner, icons).
3818
+ Rules: plain CSS in styles.css (tokens.css optional, loaded first) \u2014 no Tailwind, no imports beyond react/react-dom. disabled buttons are unfocusable; the spinner honors prefers-reduced-motion (animation: none under the media query). Interactive states are REAL (:hover, :focus-visible) AND statically forceable via the data-tendril-state attribute ("hover" | "focus-visible") spread onto the root; forced and real selectors must share one declaration block, e.g. :is(:hover, [data-tendril-state~="hover"]). ROOT SIZE IS YOURS AND PIXELS DO NOT CHECK IT, IN EITHER DIRECTION: the mount floors your root to the recorded box (#root > *{min-width:<recorded w>px;min-height:<recorded h>px} \u2014 a floor, never a cap) and the image the scorer compares is a CROP of that box. So a button that computes NARROWER or SHORTER is stretched back and captures byte-identically to a correct one (measured on a real <button>), and an oversized one shows only where its own border, radius or shadow lands back inside the crop. Take both root dimensions from the recorded box, never from a score. Fonts: 'Inter' is provided. Inline all SVGs (loading spinner, icons).
3681
3819
 
3682
3820
  BEHAVIORAL CONTRACT (machine-verified, gating): the root is a focusable <button> with cursor:pointer; real :hover visibly changes it; the loading spinner has a RUNNING CSS animation. Static lookalikes fail.`;
3683
3821
  COMBO_FIX = {
@@ -3757,7 +3895,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
3757
3895
  ];
3758
3896
  TASKS = {
3759
3897
  calendar: {
3760
- set: path12.join(REPO_ROOT, "examples/recordings/shadcn-poc-calendar"),
3898
+ set: path13.join(REPO_ROOT, "examples/recordings/shadcn-poc-calendar"),
3761
3899
  entry: "Calendar.tsx",
3762
3900
  configs: CALENDAR_CONFIGS,
3763
3901
  systemApi: CALENDAR_API,
@@ -3765,7 +3903,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
3765
3903
  prelude: { controls: ['[data-tendril-part="day"]'], textInputs: [] }
3766
3904
  },
3767
3905
  "shadcn-button": {
3768
- set: path12.join(REPO_ROOT, "examples/recordings/shadcn-poc-button"),
3906
+ set: path13.join(REPO_ROOT, "examples/recordings/shadcn-poc-button"),
3769
3907
  entry: "Button.tsx",
3770
3908
  configs: BUTTON_CONFIGS,
3771
3909
  systemApi: BUTTON_API,
@@ -3773,7 +3911,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
3773
3911
  prelude: { controls: ["> *"], textInputs: [] }
3774
3912
  },
3775
3913
  combobox: {
3776
- set: path12.join(REPO_ROOT, "examples/recordings/carbon-poc-combobox"),
3914
+ set: path13.join(REPO_ROOT, "examples/recordings/carbon-poc-combobox"),
3777
3915
  entry: "ComboBox.tsx",
3778
3916
  configs: COMBO_CONFIGS,
3779
3917
  systemApi: COMBO_API,
@@ -3781,7 +3919,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
3781
3919
  prelude: { controls: ['[role="option"]'], textInputs: ["input"], popover: { selector: '[role="listbox"]', trigger: "input" } }
3782
3920
  },
3783
3921
  modal: {
3784
- set: path12.join(REPO_ROOT, "examples/recordings/carbon-poc-modal"),
3922
+ set: path13.join(REPO_ROOT, "examples/recordings/carbon-poc-modal"),
3785
3923
  entry: "Modal.tsx",
3786
3924
  configs: MODAL_CONFIGS,
3787
3925
  systemApi: MODAL_API,
@@ -3792,15 +3930,15 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
3792
3930
  }
3793
3931
  });
3794
3932
 
3795
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/behavior.ts
3933
+ // packages/verify/src/behavior.ts
3796
3934
  var behavior_exports = {};
3797
3935
  __export(behavior_exports, {
3798
3936
  checkBehaviors: () => checkBehaviors,
3799
3937
  compileMount: () => compileMount,
3800
3938
  recordingIsDark: () => recordingIsDark
3801
3939
  });
3802
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
3803
- import path13 from "node:path";
3940
+ import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
3941
+ import path14 from "node:path";
3804
3942
  import { build as build3 } from "esbuild";
3805
3943
  import { chromium as chromium3 } from "playwright-core";
3806
3944
  import { PNG as PNG2 } from "pngjs";
@@ -3809,12 +3947,12 @@ function getFontFaces() {
3809
3947
  return _fontFaces;
3810
3948
  }
3811
3949
  async function compileMount(task, bundleDir) {
3812
- const entryTsx = path13.join(bundleDir, task.entry);
3813
- if (!existsSync8(entryTsx)) return { error: `${task.entry} missing` };
3950
+ const entryTsx = path14.join(bundleDir, task.entry);
3951
+ if (!existsSync9(entryTsx)) return { error: `${task.entry} missing` };
3814
3952
  const mountSrc = `
3815
3953
  import { createElement } from "react";
3816
3954
  import { createRoot } from "react-dom/client";
3817
- import * as B from ${JSON.stringify(path13.resolve(entryTsx))};
3955
+ import * as B from ${JSON.stringify(path14.resolve(entryTsx))};
3818
3956
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; spyProps?: string[] } }).__cfg;
3819
3957
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
3820
3958
  // Callbacks cannot ride the JSON config: specs NAME spy props and the
@@ -4066,8 +4204,8 @@ async function runSteps(page, spec, renderPose) {
4066
4204
  function recordingIsDark(task) {
4067
4205
  const rep = task.configs[0]?.rep;
4068
4206
  if (rep === void 0) return false;
4069
- const f = path13.join(task.set, rep, "get_screenshot.json");
4070
- if (!existsSync8(f)) return false;
4207
+ const f = path14.join(task.set, rep, "get_screenshot.json");
4208
+ if (!existsSync9(f)) return false;
4071
4209
  try {
4072
4210
  const env = JSON.parse(readFileSync6(f, "utf8")).content.find((c) => c.type === "image");
4073
4211
  if (env?.data === void 0) return false;
@@ -4141,7 +4279,7 @@ async function checkBehaviors(task, bundleDir, opts = {}) {
4141
4279
  const deadlineMs = timeoutMs + 1e4;
4142
4280
  const js = await compileMount(task, bundleDir);
4143
4281
  if (typeof js !== "string") return task.behaviors.map((b) => ({ id: b.id, pass: false, detail: js.error }));
4144
- const css = ["tokens.css", "styles.css"].map((f) => path13.join(bundleDir, f)).filter((f) => existsSync8(f)).map((f) => readFileSync6(f, "utf8")).join("\n");
4282
+ const css = ["tokens.css", "styles.css"].map((f) => path14.join(bundleDir, f)).filter((f) => existsSync9(f)).map((f) => readFileSync6(f, "utf8")).join("\n");
4145
4283
  const server = await chromium3.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
4146
4284
  const browser = await chromium3.connect(server.wsEndpoint());
4147
4285
  const results = [];
@@ -4248,7 +4386,7 @@ body{margin:0;padding:20px}
4248
4386
  }
4249
4387
  var _fontFaces, ANIMATED_PROBE, COMMIT_GAP_CLOSED, POSE_GAP_FLOOR, PRELUDE_ROOT_PROPS;
4250
4388
  var init_behavior = __esm({
4251
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/behavior.ts"() {
4389
+ "packages/verify/src/behavior.ts"() {
4252
4390
  "use strict";
4253
4391
  init_runtime();
4254
4392
  init_browser();
@@ -4283,9 +4421,9 @@ var init_behavior = __esm({
4283
4421
  }
4284
4422
  });
4285
4423
 
4286
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/bundle-quality.ts
4287
- import { readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync5, existsSync as existsSync9 } from "node:fs";
4288
- import path14 from "node:path";
4424
+ // packages/verify/src/bundle-quality.ts
4425
+ import { readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "node:fs";
4426
+ import path15 from "node:path";
4289
4427
  function definedVars(tokensCss) {
4290
4428
  if (tokensCss === void 0) return void 0;
4291
4429
  const names = /* @__PURE__ */ new Set();
@@ -4294,7 +4432,7 @@ function definedVars(tokensCss) {
4294
4432
  }
4295
4433
  function recordedTokenMapEmpty(setDir, reps) {
4296
4434
  const readMap = (file) => {
4297
- if (!existsSync9(file)) return void 0;
4435
+ if (!existsSync10(file)) return void 0;
4298
4436
  try {
4299
4437
  const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync7(file, "utf8"))) || "{}");
4300
4438
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
@@ -4302,28 +4440,93 @@ function recordedTokenMapEmpty(setDir, reps) {
4302
4440
  return {};
4303
4441
  }
4304
4442
  };
4305
- const setLevel = readMap(path14.join(setDir, "get_variable_defs.json"));
4443
+ const setLevel = readMap(path15.join(setDir, "get_variable_defs.json"));
4306
4444
  if (setLevel !== void 0) return Object.keys(setLevel).length === 0;
4307
4445
  let recorded = false;
4308
4446
  for (const rep of reps) {
4309
- const m = readMap(path14.join(setDir, rep, "get_variable_defs.json"));
4447
+ const m = readMap(path15.join(setDir, rep, "get_variable_defs.json"));
4310
4448
  if (m === void 0) continue;
4311
4449
  recorded = true;
4312
4450
  if (Object.keys(m).length > 0) return false;
4313
4451
  }
4314
4452
  return recorded;
4315
4453
  }
4316
- async function checkBundleQuality(bundleDir, entry, set) {
4454
+ function scannable(css) {
4455
+ const blank = (m) => m.replace(/[^\n]/g, " ");
4456
+ return css.replace(/\/\*[^]*?\*\//g, blank).replace(/@font-face\s*{[^}]*}/g, blank);
4457
+ }
4458
+ function usedWeights(css) {
4459
+ const out = /* @__PURE__ */ new Set();
4460
+ for (const m of css.matchAll(/font-weight\s*:\s*([^;}]+)/g)) {
4461
+ const v = declValue(m[1]).toLowerCase();
4462
+ if (v === "bold") out.add(700);
4463
+ else if (v === "normal") out.add(400);
4464
+ else if (/^\d{1,4}$/.test(v)) out.add(Number(v));
4465
+ }
4466
+ return [...out].sort((a, b) => a - b);
4467
+ }
4468
+ function fontStacks(css) {
4469
+ const out = [];
4470
+ for (const m of css.matchAll(/font-family\s*:\s*([^;}]+)/g)) {
4471
+ const text = declValue(m[1]);
4472
+ const families = text.split(",").map((f) => f.trim().replace(/^['"]|['"]$/g, "")).filter((f) => f !== "");
4473
+ if (families.length > 0) out.push({ families, line: css.slice(0, m.index).split("\n").length, text });
4474
+ }
4475
+ return out;
4476
+ }
4477
+ function fontStackFindings(sheets, coverage) {
4478
+ if (coverage.size === 0) return [];
4479
+ const scanned = sheets.map((s) => ({ file: s.file, css: scannable(s.css) }));
4480
+ const weights = [...new Set(scanned.flatMap((s) => usedWeights(s.css)))].sort((a, b) => a - b);
4481
+ if (weights.length === 0) return [];
4317
4482
  const findings = [];
4318
- const entryPath = path14.join(bundleDir, entry);
4319
- const cssPath = path14.join(bundleDir, "styles.css");
4320
- const tokensPath = path14.join(bundleDir, "tokens.css");
4321
- const css = existsSync9(cssPath) ? readFileSync7(cssPath, "utf8") : "";
4322
- const tokensCss = existsSync9(tokensPath) ? readFileSync7(tokensPath, "utf8") : void 0;
4323
- if (existsSync9(entryPath)) {
4483
+ const seen = /* @__PURE__ */ new Set();
4484
+ for (const sheet of scanned) {
4485
+ for (const stack of fontStacks(sheet.css)) {
4486
+ if (seen.has(stack.text)) continue;
4487
+ seen.add(stack.text);
4488
+ const family = stack.families[0];
4489
+ const bound = coverage.get(family.toLowerCase());
4490
+ if (bound === void 0) continue;
4491
+ const unserved = weights.filter((w) => !servesWeight(bound, w));
4492
+ if (unserved.length === 0) continue;
4493
+ const rescue = stack.families.slice(1).find((f) => {
4494
+ const c = coverage.get(f.toLowerCase());
4495
+ return c !== void 0 && unserved.every((w) => servesWeight(c, w));
4496
+ });
4497
+ const has = bound.span ? "100\u2013700" : [...bound.weights].sort((a, b) => a - b).join(", ");
4498
+ const list = unserved.join(", ");
4499
+ const renders = !bound.span && bound.weights.size === 1 ? `renders in the ${[...bound.weights][0]} face` : "renders in whichever of those faces is nearest";
4500
+ findings.push({
4501
+ kind: "font-stack",
4502
+ file: sheet.file,
4503
+ line: stack.line,
4504
+ message: `font-family ${stack.text} binds to '${family}', which the font kit provides at ${has} \u2014 CSS matches the FAMILY first and picks a weight only inside it (a later family is never consulted for a missing weight, and the prelude's font-synthesis: none rules out faux-bold), so text at weight ${list} ${renders}, silently. ` + (rescue !== void 0 ? `'${rescue}' in this same stack provides ${list}: list it first.` : `No family in this stack provides ${list} \u2014 if the recording shows ${unserved.length === 1 ? "that weight" : "those weights"}, run \`tendril fonts resolve "${family}" --weights ${unserved.join(" ")}\` and re-score.`) + " ADVISORY: the finding gates nothing \u2014 but the wrong face changes pixels, and this fix alone took a measured run from 7 to 9 certified."
4505
+ });
4506
+ }
4507
+ }
4508
+ return findings;
4509
+ }
4510
+ async function checkBundleQuality(bundleDir, entry, set, fontManifest) {
4511
+ const findings = [];
4512
+ const entryPath = path15.join(bundleDir, entry);
4513
+ const cssPath = path15.join(bundleDir, "styles.css");
4514
+ const tokensPath = path15.join(bundleDir, "tokens.css");
4515
+ const css = existsSync10(cssPath) ? readFileSync7(cssPath, "utf8") : "";
4516
+ const tokensCss = existsSync10(tokensPath) ? readFileSync7(tokensPath, "utf8") : void 0;
4517
+ findings.push(
4518
+ ...fontStackFindings(
4519
+ [
4520
+ { file: "styles.css", css },
4521
+ ...tokensCss === void 0 ? [] : [{ file: "tokens.css", css: tokensCss }]
4522
+ ],
4523
+ injectedFamilyWeights(fontManifest)
4524
+ )
4525
+ );
4526
+ if (existsSync10(entryPath)) {
4324
4527
  const workDir = newScratchDir("quality");
4325
4528
  try {
4326
- const tsxPath = path14.join(workDir, entry);
4529
+ const tsxPath = path15.join(workDir, entry);
4327
4530
  writeFileSync5(tsxPath, readFileSync7(entryPath, "utf8"));
4328
4531
  for (const d of runTscStrict([tsxPath]).diagnostics) {
4329
4532
  findings.push({ kind: "tsc", file: entry, ...d.line === void 0 ? {} : { line: d.line }, message: `TS${d.code}: ${d.message}` });
@@ -4340,17 +4543,20 @@ async function checkBundleQuality(bundleDir, entry, set) {
4340
4543
  }
4341
4544
  return { findings, tokensAbsent: tokensCss === void 0 && !/var\(\s*--/.test(css) };
4342
4545
  }
4546
+ var declValue;
4343
4547
  var init_bundle_quality = __esm({
4344
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/bundle-quality.ts"() {
4548
+ "packages/verify/src/bundle-quality.ts"() {
4345
4549
  "use strict";
4346
4550
  init_src();
4551
+ init_font_faces();
4347
4552
  init_runtime();
4348
4553
  init_tsc_check();
4349
4554
  init_token_lint();
4555
+ declValue = (raw) => raw.trim().replace(/\s*!important$/i, "").trim();
4350
4556
  }
4351
4557
  });
4352
4558
 
4353
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/effect-geometry.ts
4559
+ // packages/verify/src/effect-geometry.ts
4354
4560
  function fold(into, dx, dy, radius, spread) {
4355
4561
  const reach = radius + spread;
4356
4562
  into.top = Math.max(into.top, reach - dy);
@@ -4382,15 +4588,15 @@ function extentsExplainPad(near, far, pad, tolerance = 2) {
4382
4588
  }
4383
4589
  var NO_EXTENTS;
4384
4590
  var init_effect_geometry = __esm({
4385
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/effect-geometry.ts"() {
4591
+ "packages/verify/src/effect-geometry.ts"() {
4386
4592
  "use strict";
4387
4593
  NO_EXTENTS = { top: 0, right: 0, bottom: 0, left: 0 };
4388
4594
  }
4389
4595
  });
4390
4596
 
4391
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/bundle-score.ts
4392
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
4393
- import path15 from "node:path";
4597
+ // packages/verify/src/bundle-score.ts
4598
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
4599
+ import path16 from "node:path";
4394
4600
  import { build as build4 } from "esbuild";
4395
4601
  import { chromium as chromium4 } from "playwright-core";
4396
4602
  function getFontFaces2() {
@@ -4444,7 +4650,7 @@ function chooseSeat(cropAt, ref, padW, padH, backdrop, origin, extents) {
4444
4650
  }
4445
4651
  function metadataRoot(set, rep) {
4446
4652
  try {
4447
- const text = JSON.parse(readFileSync8(path15.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
4653
+ const text = JSON.parse(readFileSync8(path16.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
4448
4654
  return parseMetadataStructure(text);
4449
4655
  } catch {
4450
4656
  return void 0;
@@ -4499,17 +4705,17 @@ function smallSemanticNodes(set, rep, maxArea = 1024) {
4499
4705
  });
4500
4706
  }
4501
4707
  function repMeta(set, rep) {
4502
- const text = JSON.parse(readFileSync8(path15.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
4708
+ const text = JSON.parse(readFileSync8(path16.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
4503
4709
  const root = parseMetadataStructure(text);
4504
4710
  return { w: Math.round(root.width ?? 100), h: Math.round(root.height ?? 40) };
4505
4711
  }
4506
4712
  function repRef(set, rep) {
4507
- const env = JSON.parse(readFileSync8(path15.join(set, rep, "get_screenshot.json"), "utf8")).content.find((c) => c.type === "image");
4713
+ const env = JSON.parse(readFileSync8(path16.join(set, rep, "get_screenshot.json"), "utf8")).content.find((c) => c.type === "image");
4508
4714
  return Uint8Array.from(Buffer.from(env?.data ?? "", "base64"));
4509
4715
  }
4510
4716
  function repEffectExtents(set, rep) {
4511
- const file = path15.join(set, rep, "get_design_context.json");
4512
- if (!existsSync10(file)) return void 0;
4717
+ const file = path16.join(set, rep, "get_design_context.json");
4718
+ if (!existsSync11(file)) return void 0;
4513
4719
  try {
4514
4720
  const text = JSON.parse(readFileSync8(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
4515
4721
  const extents = shadowExtents(text);
@@ -4522,13 +4728,13 @@ async function scoreBundleForTask(task, bundleDir, bar = BAR, opts = {}) {
4522
4728
  if (opts.evidenceDir !== void 0) mkdirSync3(opts.evidenceDir, { recursive: true });
4523
4729
  const CONFIGS2 = task.configs;
4524
4730
  const timeoutMs = opts.mountDeadlineMs ?? ENGINE_MOUNT_DEADLINE_MS;
4525
- const entryTsx = path15.join(bundleDir, task.entry);
4526
- if (!existsSync10(entryTsx)) return CONFIGS2.map((c) => ({ rep: c.rep, similarity: 0, inkRecall: 0, exact: { similarity: 0, inkRecall: 0 }, pass: false, error: `${task.entry} missing` }));
4527
- const css = ["tokens.css", "styles.css"].map((f) => path15.join(bundleDir, f)).filter((f) => existsSync10(f)).map((f) => readFileSync8(f, "utf8")).join("\n");
4731
+ const entryTsx = path16.join(bundleDir, task.entry);
4732
+ if (!existsSync11(entryTsx)) return CONFIGS2.map((c) => ({ rep: c.rep, similarity: 0, inkRecall: 0, exact: { similarity: 0, inkRecall: 0 }, pass: false, error: `${task.entry} missing` }));
4733
+ const css = ["tokens.css", "styles.css"].map((f) => path16.join(bundleDir, f)).filter((f) => existsSync11(f)).map((f) => readFileSync8(f, "utf8")).join("\n");
4528
4734
  const mountSrc = `
4529
4735
  import { createElement } from "react";
4530
4736
  import { createRoot } from "react-dom/client";
4531
- import * as B from ${JSON.stringify(path15.resolve(entryTsx))};
4737
+ import * as B from ${JSON.stringify(path16.resolve(entryTsx))};
4532
4738
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
4533
4739
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
4534
4740
  const root = document.getElementById("root");
@@ -4623,12 +4829,12 @@ body{margin:0;background:rgb(${backdrop.join(",")})}
4623
4829
  return name === void 0 ? c : { ...c, name };
4624
4830
  });
4625
4831
  if (opts.evidenceDir !== void 0) {
4626
- writeFileSync6(path15.join(opts.evidenceDir, `${cfg.rep}-render.png`), scored);
4627
- writeFileSync6(path15.join(opts.evidenceDir, `${cfg.rep}-ref.png`), ref);
4628
- writeFileSync6(path15.join(opts.evidenceDir, `${cfg.rep}-diff.png`), diffPng(scored, ref));
4832
+ writeFileSync6(path16.join(opts.evidenceDir, `${cfg.rep}-render.png`), scored);
4833
+ writeFileSync6(path16.join(opts.evidenceDir, `${cfg.rep}-ref.png`), ref);
4834
+ writeFileSync6(path16.join(opts.evidenceDir, `${cfg.rep}-diff.png`), diffPng(scored, ref));
4629
4835
  for (const [i, c] of absent.slice(0, 3).entries()) {
4630
- writeFileSync6(path15.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-ref.png`), zoomCrop(ref, c));
4631
- writeFileSync6(path15.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-render.png`), zoomCrop(scored, c));
4836
+ writeFileSync6(path16.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-ref.png`), zoomCrop(ref, c));
4837
+ writeFileSync6(path16.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-render.png`), zoomCrop(scored, c));
4632
4838
  }
4633
4839
  }
4634
4840
  return {
@@ -4688,7 +4894,7 @@ body{margin:0;background:rgb(${backdrop.join(",")})}
4688
4894
  }
4689
4895
  var BAR, MARGIN, _fontFaces2, CONFIGS, SEAT_WINDOW, EFFECT_BLEED_MAX;
4690
4896
  var init_bundle_score = __esm({
4691
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/bundle-score.ts"() {
4897
+ "packages/verify/src/bundle-score.ts"() {
4692
4898
  "use strict";
4693
4899
  init_runtime();
4694
4900
  init_browser();
@@ -4706,11 +4912,11 @@ var init_bundle_score = __esm({
4706
4912
  }
4707
4913
  });
4708
4914
 
4709
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/containment.ts
4915
+ // packages/verify/src/containment.ts
4710
4916
  import { build as build5 } from "esbuild";
4711
4917
  import { chromium as chromium5 } from "playwright-core";
4712
4918
  var init_containment = __esm({
4713
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/containment.ts"() {
4919
+ "packages/verify/src/containment.ts"() {
4714
4920
  "use strict";
4715
4921
  init_runtime();
4716
4922
  init_browser();
@@ -4720,14 +4926,14 @@ var init_containment = __esm({
4720
4926
  }
4721
4927
  });
4722
4928
 
4723
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/prelude.ts
4929
+ // packages/verify/src/prelude.ts
4724
4930
  var PRELUDE_CONTRACT;
4725
4931
  var init_prelude = __esm({
4726
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/prelude.ts"() {
4932
+ "packages/verify/src/prelude.ts"() {
4727
4933
  "use strict";
4728
4934
  PRELUDE_CONTRACT = `BUNDLE PRELUDE (machine-verified on computed styles \u2014 the harness page provides NONE of this; your CSS must):
4729
4935
  - Containment reset on the component subtree: margin 0, box-sizing border-box, line-height/letter-spacing/font-family inherited from the root (host globals must not leak in).
4730
- - Component root: -webkit-font-smoothing: antialiased and -moz-osx-font-smoothing: grayscale (recorded rasterization); font-synthesis: none (a missing font weight must fall back visibly, never fake-bold); color-scheme MATCHING YOUR RECORDING (light for a light capture, dark for a dark one) and direction: ltr \u2014 pin them, so the render cannot follow the viewer OS preference and drift from the capture it is graded against; isolation: isolate (own stacking context; overlay z-indexes never fight the host).
4936
+ - Component root: -webkit-font-smoothing: antialiased and -moz-osx-font-smoothing: grayscale (recorded rasterization); font-synthesis: none (no faux bold or italic \u2014 text renders in the face your stack actually binds, so a weight the kit cannot serve reaches the score instead of being faked; it does NOT fall through to the next family in the stack); color-scheme MATCHING YOUR RECORDING (light for a light capture, dark for a dark one) and direction: ltr \u2014 pin them, so the render cannot follow the viewer OS preference and drift from the capture it is graded against; isolation: isolate (own stacking context; overlay z-indexes never fight the host).
4731
4937
  - Interactive controls (buttons, options): touch-action: manipulation and user-select: none. Text inputs stay selectable (never user-select: none on them).
4732
4938
  - Scrollable popovers/menus: overscroll-behavior: contain.
4733
4939
  - Focus indicators bind to :focus-visible, never bare :focus. If the recording contains a focus pose, style the indicator from that recorded truth. If NO focus pose is recorded, do NOT invent ring colors/widths/offsets \u2014 an invented ring is unrecorded pixels; keep the browser's default indicator for keyboard focus and state the gap in your report. Text inputs match :focus-visible even on mouse click BY SPEC; the ONE permitted refinement is suppressing the indicator on a POSITIVELY OBSERVED pointer press, fail-safe toward showing it (programmatic focus, restored focus, and assistive tech all count as keyboard and keep the ring \u2014 WCAG 2.4.7 binds keyboard operation). Never suppress more broadly than an observed press, and never restyle what you kept.
@@ -4735,9 +4941,9 @@ var init_prelude = __esm({
4735
4941
  }
4736
4942
  });
4737
4943
 
4738
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/parity.ts
4739
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
4740
- import path16 from "node:path";
4944
+ // packages/verify/src/parity.ts
4945
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
4946
+ import path17 from "node:path";
4741
4947
  import { chromium as chromium6 } from "playwright-core";
4742
4948
  function getFontFaces3() {
4743
4949
  _fontFaces3 ??= fontFaceCss();
@@ -4766,7 +4972,7 @@ async function checkHoverParity(task, bundleDir, opts = {}) {
4766
4972
  const deadlineMs = timeoutMs + 1e4;
4767
4973
  const js = await compileMount(task, bundleDir);
4768
4974
  if (typeof js !== "string") return configs.map((c) => ({ id: `parity:${c.rep}`, pass: false, detail: js.error }));
4769
- const css = ["tokens.css", "styles.css"].map((f) => path16.join(bundleDir, f)).filter((f) => existsSync11(f)).map((f) => readFileSync9(f, "utf8")).join("\n");
4975
+ const css = ["tokens.css", "styles.css"].map((f) => path17.join(bundleDir, f)).filter((f) => existsSync12(f)).map((f) => readFileSync9(f, "utf8")).join("\n");
4770
4976
  const server = await chromium6.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
4771
4977
  const browser = await chromium6.connect(server.wsEndpoint());
4772
4978
  const results = [];
@@ -4835,7 +5041,7 @@ body{margin:0;padding:20px}
4835
5041
  }
4836
5042
  var _fontFaces3;
4837
5043
  var init_parity = __esm({
4838
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/parity.ts"() {
5044
+ "packages/verify/src/parity.ts"() {
4839
5045
  "use strict";
4840
5046
  init_browser();
4841
5047
  init_behavior();
@@ -4844,10 +5050,10 @@ var init_parity = __esm({
4844
5050
  }
4845
5051
  });
4846
5052
 
4847
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/composition.ts
5053
+ // packages/verify/src/composition.ts
4848
5054
  import { createRequire as createRequire2 } from "node:module";
4849
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
4850
- import path17 from "node:path";
5055
+ import { existsSync as existsSync13, readFileSync as readFileSync10 } from "node:fs";
5056
+ import path18 from "node:path";
4851
5057
  import { build as build6 } from "esbuild";
4852
5058
  import { chromium as chromium7 } from "playwright-core";
4853
5059
  function getFontFaces4() {
@@ -4855,9 +5061,9 @@ function getFontFaces4() {
4855
5061
  return _fontFaces4;
4856
5062
  }
4857
5063
  async function compileInstrumentedMount(task, bundleDir) {
4858
- const entryTsx = path17.join(bundleDir, task.entry);
4859
- if (!existsSync12(entryTsx)) return { error: `${task.entry} missing` };
4860
- const requireFromVerify = createRequire2(path17.join(VERIFY_PKG_DIR, "package.json"));
5064
+ const entryTsx = path18.join(bundleDir, task.entry);
5065
+ if (!existsSync13(entryTsx)) return { error: `${task.entry} missing` };
5066
+ const requireFromVerify = createRequire2(path18.join(VERIFY_PKG_DIR, "package.json"));
4861
5067
  let realJsxPath;
4862
5068
  try {
4863
5069
  realJsxPath = requireFromVerify.resolve("react/jsx-runtime");
@@ -4868,7 +5074,7 @@ async function compileInstrumentedMount(task, bundleDir) {
4868
5074
  import { createElement } from "react";
4869
5075
  import { createRoot } from "react-dom/client";
4870
5076
  import { __registerParts } from "react/jsx-runtime";
4871
- import * as B from ${JSON.stringify(path17.resolve(entryTsx))};
5077
+ import * as B from ${JSON.stringify(path18.resolve(entryTsx))};
4872
5078
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; partComponents: string[] } }).__cfg;
4873
5079
  const pairs: Array<[unknown, string]> = [];
4874
5080
  for (const name of cfg.partComponents) {
@@ -4923,7 +5129,7 @@ function expectedParts(task, roles, mainSlug) {
4923
5129
  }
4924
5130
  function interiorRegions(setDir, roles) {
4925
5131
  const mains = roles.main;
4926
- const withInterior = mains.filter((m) => existsSync12(path17.join(setDir, m, "get_metadata_interior.json")));
5132
+ const withInterior = mains.filter((m) => existsSync13(path18.join(setDir, m, "get_metadata_interior.json")));
4927
5133
  if (withInterior.length === 0) {
4928
5134
  return { unavailable: "no interior geometry recorded for any main (pre-obligation set) \u2014 re-record with `tendril record` to enable crop checks" };
4929
5135
  }
@@ -4940,7 +5146,7 @@ async function checkCropComposition(task, bundleDir, regions, opts = {}) {
4940
5146
  if (typeof js !== "string") {
4941
5147
  return regions.map((r) => ({ id: cropId(r), pass: false, similarity: 0, inkRecall: 0, detail: js.error }));
4942
5148
  }
4943
- const css = ["tokens.css", "styles.css"].map((f) => path17.join(bundleDir, f)).filter((f) => existsSync12(f)).map((f) => readFileSync10(f, "utf8")).join("\n");
5149
+ const css = ["tokens.css", "styles.css"].map((f) => path18.join(bundleDir, f)).filter((f) => existsSync13(f)).map((f) => readFileSync10(f, "utf8")).join("\n");
4944
5150
  const server = await chromium7.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
4945
5151
  const browser = await chromium7.connect(server.wsEndpoint());
4946
5152
  const PAD = 4;
@@ -5045,7 +5251,7 @@ async function checkStructuralComposition(task, bundleDir, roles, opts = {}) {
5045
5251
  const deadlineMs = timeoutMs + 1e4;
5046
5252
  const js = await compileInstrumentedMount(task, bundleDir);
5047
5253
  if (typeof js !== "string") return [...results, ...mains.map((m) => ({ id: `composition:${m}`, pass: false, detail: js.error }))];
5048
- const css = ["tokens.css", "styles.css"].map((f) => path17.join(bundleDir, f)).filter((f) => existsSync12(f)).map((f) => readFileSync10(f, "utf8")).join("\n");
5254
+ const css = ["tokens.css", "styles.css"].map((f) => path18.join(bundleDir, f)).filter((f) => existsSync13(f)).map((f) => readFileSync10(f, "utf8")).join("\n");
5049
5255
  const server = await chromium7.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
5050
5256
  const browser = await chromium7.connect(server.wsEndpoint());
5051
5257
  try {
@@ -5110,7 +5316,7 @@ body{margin:0;padding:20px}
5110
5316
  }
5111
5317
  var _fontFaces4, REAL_JSX_SPEC, JSX_SHIM, CROP_BAR, cropId;
5112
5318
  var init_composition = __esm({
5113
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/composition.ts"() {
5319
+ "packages/verify/src/composition.ts"() {
5114
5320
  "use strict";
5115
5321
  init_runtime();
5116
5322
  init_browser();
@@ -5134,18 +5340,18 @@ export const jsxs = (t, p, k) => real.jsxs(t, inject(t, p), k);
5134
5340
  }
5135
5341
  });
5136
5342
 
5137
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/occlusion.ts
5138
- import { existsSync as existsSync13 } from "node:fs";
5139
- import path18 from "node:path";
5343
+ // packages/verify/src/occlusion.ts
5344
+ import { existsSync as existsSync14 } from "node:fs";
5345
+ import path19 from "node:path";
5140
5346
  import { build as build7 } from "esbuild";
5141
5347
  import { chromium as chromium8 } from "playwright-core";
5142
5348
  async function compileTwoUp(task, bundleDir) {
5143
- const entryTsx = path18.join(bundleDir, task.entry);
5144
- if (!existsSync13(entryTsx)) return { error: `${task.entry} missing` };
5349
+ const entryTsx = path19.join(bundleDir, task.entry);
5350
+ if (!existsSync14(entryTsx)) return { error: `${task.entry} missing` };
5145
5351
  const src = `
5146
5352
  import { createElement } from "react";
5147
5353
  import { createRoot } from "react-dom/client";
5148
- import * as B from ${JSON.stringify(path18.resolve(entryTsx))};
5354
+ import * as B from ${JSON.stringify(path19.resolve(entryTsx))};
5149
5355
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
5150
5356
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
5151
5357
  for (const id of ["first", "second"]) {
@@ -5283,7 +5489,7 @@ body{margin:0;padding:16px;display:flex;flex-direction:column;gap:8px;align-item
5283
5489
  }
5284
5490
  var OVERLAY_SELECTOR;
5285
5491
  var init_occlusion = __esm({
5286
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/occlusion.ts"() {
5492
+ "packages/verify/src/occlusion.ts"() {
5287
5493
  "use strict";
5288
5494
  init_runtime();
5289
5495
  init_browser();
@@ -5292,9 +5498,9 @@ var init_occlusion = __esm({
5292
5498
  }
5293
5499
  });
5294
5500
 
5295
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/index.ts
5501
+ // packages/verify/src/index.ts
5296
5502
  var init_src4 = __esm({
5297
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/verify/src/index.ts"() {
5503
+ "packages/verify/src/index.ts"() {
5298
5504
  "use strict";
5299
5505
  init_browser();
5300
5506
  init_runtime();
@@ -5322,22 +5528,22 @@ var init_src4 = __esm({
5322
5528
  }
5323
5529
  });
5324
5530
 
5325
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/environment.ts
5326
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "node:fs";
5327
- import path19 from "node:path";
5531
+ // packages/cli/src/environment.ts
5532
+ import { existsSync as existsSync15, readFileSync as readFileSync11 } from "node:fs";
5533
+ import path20 from "node:path";
5328
5534
  import { createHash as createHash3 } from "node:crypto";
5329
- import { fileURLToPath as fileURLToPath4 } from "node:url";
5535
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
5330
5536
  function cliVersion() {
5331
5537
  try {
5332
- return JSON.parse(readFileSync11(path19.join(path19.dirname(fileURLToPath4(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
5538
+ return JSON.parse(readFileSync11(path20.join(path20.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
5333
5539
  } catch {
5334
5540
  return "dev";
5335
5541
  }
5336
5542
  }
5337
5543
  function environmentStamp(taskFamilies) {
5338
- const manifestPath2 = path19.join(fontCacheDir(), "manifest.json");
5544
+ const manifestPath2 = path20.join(fontCacheDir(), "manifest.json");
5339
5545
  let fontsHash = null;
5340
- if (existsSync14(manifestPath2)) {
5546
+ if (existsSync15(manifestPath2)) {
5341
5547
  try {
5342
5548
  const entries = JSON.parse(readFileSync11(manifestPath2, "utf8"));
5343
5549
  const scoped = taskFamilies === void 0 || taskFamilies === null ? entries : entries.filter((f) => taskFamilies.some((fam) => fam.toLowerCase() === f.family.toLowerCase()));
@@ -5357,20 +5563,20 @@ function environmentStamp(taskFamilies) {
5357
5563
  return { chrome, chromeVersion: ver === null ? null : ver + lcdSuffix, fontsManifestSha256: fontsHash };
5358
5564
  }
5359
5565
  var init_environment = __esm({
5360
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/environment.ts"() {
5566
+ "packages/cli/src/environment.ts"() {
5361
5567
  "use strict";
5362
5568
  init_src4();
5363
5569
  }
5364
5570
  });
5365
5571
 
5366
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/describe.ts
5572
+ // packages/cli/src/describe.ts
5367
5573
  function printDescription(description) {
5368
5574
  process.stdout.write(`${JSON.stringify(description, null, 2)}
5369
5575
  `);
5370
5576
  }
5371
5577
  var COMMON_EXIT_CODES;
5372
5578
  var init_describe = __esm({
5373
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/describe.ts"() {
5579
+ "packages/cli/src/describe.ts"() {
5374
5580
  "use strict";
5375
5581
  COMMON_EXIT_CODES = {
5376
5582
  0: "success",
@@ -5382,9 +5588,9 @@ var init_describe = __esm({
5382
5588
  }
5383
5589
  });
5384
5590
 
5385
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/env.ts
5386
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "node:fs";
5387
- import path20 from "node:path";
5591
+ // packages/cli/src/env.ts
5592
+ import { existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
5593
+ import path21 from "node:path";
5388
5594
  function parseEnv(content) {
5389
5595
  const entries = /* @__PURE__ */ new Map();
5390
5596
  for (const line of content.split("\n")) {
@@ -5396,17 +5602,17 @@ function parseEnv(content) {
5396
5602
  function resolveCredential(name) {
5397
5603
  const fromProcess = process.env[name];
5398
5604
  if (fromProcess) return fromProcess;
5399
- const envPath = path20.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
5400
- if (!existsSync15(envPath)) return void 0;
5605
+ const envPath = path21.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
5606
+ if (!existsSync16(envPath)) return void 0;
5401
5607
  return parseEnv(readFileSync12(envPath, "utf8")).get(name);
5402
5608
  }
5403
5609
  var init_env = __esm({
5404
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/env.ts"() {
5610
+ "packages/cli/src/env.ts"() {
5405
5611
  "use strict";
5406
5612
  }
5407
5613
  });
5408
5614
 
5409
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/output.ts
5615
+ // packages/cli/src/output.ts
5410
5616
  var output_exports = {};
5411
5617
  __export(output_exports, {
5412
5618
  PROGRESS_PREFIX: () => PROGRESS_PREFIX,
@@ -5450,23 +5656,23 @@ function warn(flags, message) {
5450
5656
  }
5451
5657
  var PROGRESS_PREFIX;
5452
5658
  var init_output = __esm({
5453
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/output.ts"() {
5659
+ "packages/cli/src/output.ts"() {
5454
5660
  "use strict";
5455
5661
  init_src3();
5456
5662
  PROGRESS_PREFIX = "@tendril-progress ";
5457
5663
  }
5458
5664
  });
5459
5665
 
5460
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/entitlement.ts
5461
- import { chmodSync, existsSync as existsSync16, mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "node:fs";
5666
+ // packages/cli/src/entitlement.ts
5667
+ import { chmodSync, existsSync as existsSync17, mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "node:fs";
5462
5668
  import crypto from "node:crypto";
5463
5669
  import os3 from "node:os";
5464
- import path21 from "node:path";
5670
+ import path22 from "node:path";
5465
5671
  function entitlementPath() {
5466
- return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path21.join(os3.homedir(), ".tendril", "entitlement.json");
5672
+ return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path22.join(os3.homedir(), ".tendril", "entitlement.json");
5467
5673
  }
5468
5674
  function readStoredEntitlement(file = entitlementPath()) {
5469
- if (!existsSync16(file)) return void 0;
5675
+ if (!existsSync17(file)) return void 0;
5470
5676
  try {
5471
5677
  const parsed = JSON.parse(readFileSync13(file, "utf8"));
5472
5678
  if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
@@ -5476,7 +5682,7 @@ function readStoredEntitlement(file = entitlementPath()) {
5476
5682
  }
5477
5683
  }
5478
5684
  function writeStoredEntitlement(stored, file = entitlementPath()) {
5479
- mkdirSync4(path21.dirname(file), { recursive: true });
5685
+ mkdirSync4(path22.dirname(file), { recursive: true });
5480
5686
  writeFileSync7(file, `${JSON.stringify(stored, null, 2)}
5481
5687
  `);
5482
5688
  chmodSync(file, 384);
@@ -5502,11 +5708,11 @@ function checkEntitlement(opts = {}) {
5502
5708
  const now = opts.now ?? Date.now();
5503
5709
  const stored = "stored" in opts ? opts.stored : readStoredEntitlement();
5504
5710
  if (stored === void 0) {
5505
- return { ok: false, code: "entitlement-required", error: "no entitlement on this machine \u2014 record and generate need an active Tendril plan (verify stays free, always)", remediation: ACTIVATE_REMEDIATION };
5711
+ return { ok: false, code: "entitlement-required", error: "no entitlement on this machine \u2014 record and generate need an active Tendril plan (verify stays free, always)", remediation: activateRemediation() };
5506
5712
  }
5507
5713
  const parsed = parseEntitlementToken(stored.token);
5508
5714
  if ("error" in parsed) {
5509
- return { ok: false, code: "entitlement-invalid", error: `stored entitlement is unreadable: ${parsed.error}`, remediation: ACTIVATE_REMEDIATION };
5715
+ return { ok: false, code: "entitlement-invalid", error: `stored entitlement is unreadable: ${parsed.error}`, remediation: activateRemediation() };
5510
5716
  }
5511
5717
  const pem = keys[parsed.claims.kid];
5512
5718
  const valid = pem !== void 0 && (() => {
@@ -5517,14 +5723,14 @@ function checkEntitlement(opts = {}) {
5517
5723
  }
5518
5724
  })();
5519
5725
  if (!valid) {
5520
- return { ok: false, code: "entitlement-invalid", error: "stored entitlement failed signature verification", remediation: ACTIVATE_REMEDIATION };
5726
+ return { ok: false, code: "entitlement-invalid", error: "stored entitlement failed signature verification", remediation: activateRemediation() };
5521
5727
  }
5522
5728
  if (now > parsed.claims.exp + TOLERANCE_MS) {
5523
5729
  return {
5524
5730
  ok: false,
5525
5731
  code: "entitlement-expired",
5526
5732
  error: "entitlement expired (and the offline tolerance window has passed)",
5527
- remediation: `Reconnect and ${ACTIVATE_REMEDIATION}`
5733
+ remediation: `Reconnect and ${activateRemediation()}`
5528
5734
  };
5529
5735
  }
5530
5736
  if (now < stored.lastRefreshAt - CLOCK_ROLLBACK_MS) {
@@ -5532,7 +5738,7 @@ function checkEntitlement(opts = {}) {
5532
5738
  ok: false,
5533
5739
  code: "entitlement-clock",
5534
5740
  error: "system clock sits more than a day before the last entitlement refresh",
5535
- remediation: `Fix the system clock, then ${ACTIVATE_REMEDIATION}`
5741
+ remediation: `Fix the system clock, then ${activateRemediation()}`
5536
5742
  };
5537
5743
  }
5538
5744
  return { ok: true, mode: "active", claims: parsed.claims, stale: now > parsed.claims.exp };
@@ -5543,26 +5749,27 @@ function requireEntitlement(flags) {
5543
5749
  fail(flags, ExitCode.Auth, { error: status.error, code: status.code, remediation: status.remediation });
5544
5750
  }
5545
5751
  }
5546
- var ENT_PREFIX, PUBLIC_KEYS, TOLERANCE_MS, CLOCK_ROLLBACK_MS, b64urlDecode, ACTIVATE_REMEDIATION;
5752
+ var ENT_PREFIX, PUBLIC_KEYS, TOLERANCE_MS, CLOCK_ROLLBACK_MS, b64urlDecode, activateRemediation;
5547
5753
  var init_entitlement = __esm({
5548
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/entitlement.ts"() {
5754
+ "packages/cli/src/entitlement.ts"() {
5549
5755
  "use strict";
5550
5756
  init_src3();
5757
+ init_invocation();
5551
5758
  init_output();
5552
5759
  ENT_PREFIX = "tendril-ent.v1.";
5553
5760
  PUBLIC_KEYS = {};
5554
5761
  TOLERANCE_MS = 24 * 60 * 60 * 1e3;
5555
5762
  CLOCK_ROLLBACK_MS = 24 * 60 * 60 * 1e3;
5556
5763
  b64urlDecode = (s) => Buffer.from(s, "base64url");
5557
- ACTIVATE_REMEDIATION = "Run `tendril activate` in your terminal (a browser approval \u2014 never paste license material into an agent chat).";
5764
+ activateRemediation = () => `Run \`${tendrilCommand("activate")}\` in your terminal (a browser approval \u2014 never paste license material into an agent chat).`;
5558
5765
  }
5559
5766
  });
5560
5767
 
5561
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/doctor.ts
5768
+ // packages/cli/src/commands/doctor.ts
5562
5769
  import { spawnSync } from "node:child_process";
5563
- import { existsSync as existsSync17, readFileSync as readFileSync14, readdirSync as readdirSync3 } from "node:fs";
5770
+ import { existsSync as existsSync18, readFileSync as readFileSync14, readdirSync as readdirSync3 } from "node:fs";
5564
5771
  import os4 from "node:os";
5565
- import path22 from "node:path";
5772
+ import path23 from "node:path";
5566
5773
  async function runDoctorChecks(options) {
5567
5774
  const checks = [];
5568
5775
  const mcpUrl = options.mcpUrl ?? DEFAULT_MCP_URL;
@@ -5592,7 +5799,7 @@ async function runDoctorChecks(options) {
5592
5799
  name: "openrouter-key",
5593
5800
  ok: false,
5594
5801
  detail: "OPENROUTER_API_KEY not set \u2014 optional; only the curated API-model engine needs it",
5595
- remediation: "Run `tendril init` to store your OpenRouter key (BYOK) if you plan to use the curated engine."
5802
+ remediation: `Run \`${tendrilCommand("init")}\` to store your OpenRouter key (BYOK) if you plan to use the curated engine.`
5596
5803
  }
5597
5804
  );
5598
5805
  try {
@@ -5606,17 +5813,17 @@ async function runDoctorChecks(options) {
5606
5813
  remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
5607
5814
  });
5608
5815
  }
5609
- const fontManifest = path22.join(fontCacheDir(), "manifest.json");
5816
+ const fontManifest = path23.join(fontCacheDir(), "manifest.json");
5610
5817
  checks.push(
5611
- existsSync17(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync14(fontManifest, "utf8")).length} faces)` } : {
5818
+ existsSync18(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync14(fontManifest, "utf8")).length} faces)` } : {
5612
5819
  name: "font-cache",
5613
5820
  ok: true,
5614
5821
  detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
5615
- remediation: "Nothing to do now: `tendril fonts resolve --set <recording-dir>` fetches exactly what a recording declares, and generate/verify name that command when they need it."
5822
+ 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.`
5616
5823
  }
5617
5824
  );
5618
- const pluginRoot = path22.join(os4.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
5619
- if (existsSync17(pluginRoot)) {
5825
+ const pluginRoot = path23.join(os4.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
5826
+ if (existsSync18(pluginRoot)) {
5620
5827
  try {
5621
5828
  const versions = readdirSync3(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
5622
5829
  const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
@@ -5660,17 +5867,6 @@ async function runDoctorChecks(options) {
5660
5867
  });
5661
5868
  return { ok: checks.filter((c) => c.name !== "figma-pat" && c.name !== "openrouter-key" && c.name !== "path-skew").every((c) => c.ok), checks };
5662
5869
  }
5663
- function findPathTendril(pathEnv, platform) {
5664
- const dirs = pathEnv.split(path22.delimiter).filter((d) => d !== "" && !/node_modules[\\/]\.bin/.test(d) && !/[\\/]_npx[\\/]/.test(d));
5665
- const names = platform === "win32" ? ["tendril.cmd", "tendril.bat"] : ["tendril"];
5666
- for (const dir of dirs) {
5667
- for (const name of names) {
5668
- const candidate = path22.join(dir, name);
5669
- if (existsSync17(candidate)) return candidate;
5670
- }
5671
- }
5672
- return null;
5673
- }
5674
5870
  function probeVersion(binary) {
5675
5871
  const windowsShim = /\.(cmd|bat)$/i.test(binary);
5676
5872
  const res = windowsShim ? spawnSync(`"${binary}" --version`, { shell: true, timeout: 5e3, encoding: "utf8" }) : spawnSync(binary, ["--version"], { timeout: 5e3, encoding: "utf8" });
@@ -5735,13 +5931,14 @@ async function runDoctor(flags) {
5735
5931
  }
5736
5932
  var DEFAULT_MCP_URL, DOCTOR_DESCRIPTION;
5737
5933
  var init_doctor = __esm({
5738
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/doctor.ts"() {
5934
+ "packages/cli/src/commands/doctor.ts"() {
5739
5935
  "use strict";
5740
5936
  init_src4();
5741
5937
  init_src();
5742
5938
  init_describe();
5743
5939
  init_env();
5744
5940
  init_environment();
5941
+ init_invocation();
5745
5942
  init_output();
5746
5943
  init_entitlement();
5747
5944
  DEFAULT_MCP_URL = "http://127.0.0.1:3845/mcp";
@@ -5763,7 +5960,7 @@ var init_doctor = __esm({
5763
5960
  }
5764
5961
  });
5765
5962
 
5766
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/model-config.ts
5963
+ // packages/llm/src/model-config.ts
5767
5964
  import { z as z6 } from "zod";
5768
5965
  function resolveModel(config, requestedId) {
5769
5966
  const entry = config.allowlist.find((m) => m.id === requestedId);
@@ -5784,7 +5981,7 @@ function resolveModel(config, requestedId) {
5784
5981
  }
5785
5982
  var ModelStatusSchema, ModelEntrySchema, ModelConfigSchema, DEFAULT_MODEL_CONFIG;
5786
5983
  var init_model_config = __esm({
5787
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/model-config.ts"() {
5984
+ "packages/llm/src/model-config.ts"() {
5788
5985
  "use strict";
5789
5986
  ModelStatusSchema = z6.enum(["verified", "degraded"]);
5790
5987
  ModelEntrySchema = z6.object({
@@ -5873,10 +6070,10 @@ var init_model_config = __esm({
5873
6070
  }
5874
6071
  });
5875
6072
 
5876
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/openrouter.ts
6073
+ // packages/llm/src/openrouter.ts
5877
6074
  var FALLBACK_KINDS, DEFAULT_REQUEST_TIMEOUT_MS, OpenRouterClient;
5878
6075
  var init_openrouter = __esm({
5879
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/openrouter.ts"() {
6076
+ "packages/llm/src/openrouter.ts"() {
5880
6077
  "use strict";
5881
6078
  FALLBACK_KINDS = /* @__PURE__ */ new Set(["rate-limit", "server", "network", "timeout"]);
5882
6079
  DEFAULT_REQUEST_TIMEOUT_MS = 6e5;
@@ -6003,7 +6200,7 @@ var init_openrouter = __esm({
6003
6200
  }
6004
6201
  });
6005
6202
 
6006
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/semantics.ts
6203
+ // packages/llm/src/semantics.ts
6007
6204
  import { z as z7 } from "zod";
6008
6205
  function portable(node) {
6009
6206
  if (Array.isArray(node)) return node.map(portable);
@@ -6041,7 +6238,7 @@ function docsJsonSchema() {
6041
6238
  }
6042
6239
  var PropTypeSchema, SemanticsSchema, CodegenOutputSchema, DocsOutputSchema;
6043
6240
  var init_semantics = __esm({
6044
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/semantics.ts"() {
6241
+ "packages/llm/src/semantics.ts"() {
6045
6242
  "use strict";
6046
6243
  PropTypeSchema = z7.discriminatedUnion("kind", [
6047
6244
  z7.object({ kind: z7.literal("enum"), values: z7.array(z7.string()).min(1) }),
@@ -6130,10 +6327,10 @@ var init_semantics = __esm({
6130
6327
  }
6131
6328
  });
6132
6329
 
6133
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/golden/button.ts
6330
+ // packages/llm/src/golden/button.ts
6134
6331
  var goldenButtonSemantics, goldenButtonCode, goldenButtonDocs;
6135
6332
  var init_button2 = __esm({
6136
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/golden/button.ts"() {
6333
+ "packages/llm/src/golden/button.ts"() {
6137
6334
  "use strict";
6138
6335
  goldenButtonSemantics = {
6139
6336
  componentName: "Button",
@@ -6291,7 +6488,7 @@ handling come from the platform.
6291
6488
  }
6292
6489
  });
6293
6490
 
6294
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/prompt.ts
6491
+ // packages/llm/src/prompt.ts
6295
6492
  function buildStablePrefix(input) {
6296
6493
  const tokens = [...input.tokenLines].sort().join("\n");
6297
6494
  const golden = `GOLDEN EXAMPLE (canonical quality bar)
@@ -6325,7 +6522,7 @@ ${input.irJson}` });
6325
6522
  }
6326
6523
  var SYSTEM_RULES;
6327
6524
  var init_prompt = __esm({
6328
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/prompt.ts"() {
6525
+ "packages/llm/src/prompt.ts"() {
6329
6526
  "use strict";
6330
6527
  init_button2();
6331
6528
  SYSTEM_RULES = `You generate production React components from a design-system IR.
@@ -6421,10 +6618,10 @@ strictly in the requested JSON shape.`;
6421
6618
  }
6422
6619
  });
6423
6620
 
6424
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/generation-model.ts
6621
+ // packages/llm/src/generation-model.ts
6425
6622
  var MockGenerationModel, MAX_SCHEMA_ATTEMPTS, OpenRouterGenerationModel;
6426
6623
  var init_generation_model = __esm({
6427
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/generation-model.ts"() {
6624
+ "packages/llm/src/generation-model.ts"() {
6428
6625
  "use strict";
6429
6626
  init_openrouter();
6430
6627
  init_prompt();
@@ -6581,9 +6778,9 @@ ${JSON.stringify(semantics)}`,
6581
6778
  }
6582
6779
  });
6583
6780
 
6584
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/index.ts
6781
+ // packages/llm/src/index.ts
6585
6782
  var init_src5 = __esm({
6586
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/llm/src/index.ts"() {
6783
+ "packages/llm/src/index.ts"() {
6587
6784
  "use strict";
6588
6785
  init_model_config();
6589
6786
  init_openrouter();
@@ -6594,11 +6791,11 @@ var init_src5 = __esm({
6594
6791
  }
6595
6792
  });
6596
6793
 
6597
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/metadata/src/component-json.ts
6794
+ // packages/metadata/src/component-json.ts
6598
6795
  import { z as z8 } from "zod";
6599
6796
  var TokenUseSchema, AntiPatternSchema, ComponentJsonSchema;
6600
6797
  var init_component_json = __esm({
6601
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/metadata/src/component-json.ts"() {
6798
+ "packages/metadata/src/component-json.ts"() {
6602
6799
  "use strict";
6603
6800
  TokenUseSchema = z8.object({
6604
6801
  /** DTCG token path, e.g. "color.primary". */
@@ -6650,7 +6847,7 @@ var init_component_json = __esm({
6650
6847
  }
6651
6848
  });
6652
6849
 
6653
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/metadata/src/extract.ts
6850
+ // packages/metadata/src/extract.ts
6654
6851
  import { parse } from "react-docgen";
6655
6852
  function extractProps(tsxSource, filename) {
6656
6853
  const docs = parse(tsxSource, { filename });
@@ -6723,13 +6920,13 @@ function buildComponentJson(input) {
6723
6920
  });
6724
6921
  }
6725
6922
  var init_extract = __esm({
6726
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/metadata/src/extract.ts"() {
6923
+ "packages/metadata/src/extract.ts"() {
6727
6924
  "use strict";
6728
6925
  init_component_json();
6729
6926
  }
6730
6927
  });
6731
6928
 
6732
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/metadata/src/recording-set.ts
6929
+ // packages/metadata/src/recording-set.ts
6733
6930
  import { z as z9 } from "zod";
6734
6931
  function roleLossToken(loss) {
6735
6932
  return loss.kind === "main" ? `main:${loss.main}` : `part:${loss.part}${EDGE_ARROW}${loss.main}`;
@@ -6790,7 +6987,7 @@ function validateRecordingSet(manifest, fileExists) {
6790
6987
  }
6791
6988
  var RECORDING_SET_VERSION, EnvelopeSchema, RepEntrySchema, RolesSchema, EDGE_ARROW, RecordingSetManifestSchema, REQUIRED_REP_FILES;
6792
6989
  var init_recording_set = __esm({
6793
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/metadata/src/recording-set.ts"() {
6990
+ "packages/metadata/src/recording-set.ts"() {
6794
6991
  "use strict";
6795
6992
  RECORDING_SET_VERSION = 1;
6796
6993
  EnvelopeSchema = z9.object({
@@ -6851,7 +7048,7 @@ var init_recording_set = __esm({
6851
7048
  }
6852
7049
  });
6853
7050
 
6854
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/metadata/src/bundle.ts
7051
+ // packages/metadata/src/bundle.ts
6855
7052
  import { z as z10 } from "zod";
6856
7053
  function readBundleManifest(raw) {
6857
7054
  if (Buffer.byteLength(raw, "utf8") > MAX_BUNDLE_MANIFEST_BYTES) {
@@ -6920,7 +7117,7 @@ function hashRecordingSet(relPaths, readFile, sha256) {
6920
7117
  }
6921
7118
  var BUNDLE_VERSION, ConfigStatusSchema, PinnedPropSchema, PropAdapterSchema, RequiredFontSchema, ConfigClaimSchema, BehaviorClaimSchema, BundleProvenanceSchema, BundleComponentJsonSchema, MAX_BUNDLE_MANIFEST_BYTES, MAX_BUNDLE_SOURCE_BYTES;
6922
7119
  var init_bundle = __esm({
6923
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/metadata/src/bundle.ts"() {
7120
+ "packages/metadata/src/bundle.ts"() {
6924
7121
  "use strict";
6925
7122
  init_recording_set();
6926
7123
  BUNDLE_VERSION = 1;
@@ -7018,9 +7215,9 @@ var init_bundle = __esm({
7018
7215
  }
7019
7216
  });
7020
7217
 
7021
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/metadata/src/index.ts
7218
+ // packages/metadata/src/index.ts
7022
7219
  var init_src6 = __esm({
7023
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/metadata/src/index.ts"() {
7220
+ "packages/metadata/src/index.ts"() {
7024
7221
  "use strict";
7025
7222
  init_component_json();
7026
7223
  init_extract();
@@ -7029,7 +7226,7 @@ var init_src6 = __esm({
7029
7226
  }
7030
7227
  });
7031
7228
 
7032
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/activate.ts
7229
+ // packages/cli/src/commands/activate.ts
7033
7230
  var activate_exports = {};
7034
7231
  __export(activate_exports, {
7035
7232
  ENTITLEMENT_SERVICE_URL: () => ENTITLEMENT_SERVICE_URL,
@@ -7054,7 +7251,7 @@ async function runActivate(flags) {
7054
7251
  fail(flags, ExitCode.Auth, {
7055
7252
  error: `could not start activation: ${err instanceof Error ? err.message : String(err)}`,
7056
7253
  code: "entitlement-service-unreachable",
7057
- remediation: "Check your connection and retry `tendril activate`."
7254
+ remediation: `Check your connection and retry \`${tendrilCommand("activate")}\`.`
7058
7255
  });
7059
7256
  }
7060
7257
  process.stderr.write(`To activate Tendril, visit:
@@ -7081,7 +7278,7 @@ Waiting for approval\u2026
7081
7278
  fail(flags, ExitCode.Auth, {
7082
7279
  error: `activation was not approved (HTTP ${res.status})`,
7083
7280
  code: "entitlement-denied",
7084
- remediation: "Retry `tendril activate`; if it persists, check the account's plan in the portal."
7281
+ remediation: `Retry \`${tendrilCommand("activate")}\`; if it persists, check the account's plan in the portal.`
7085
7282
  });
7086
7283
  }
7087
7284
  token = (await res.json()).token;
@@ -7091,7 +7288,7 @@ Waiting for approval\u2026
7091
7288
  fail(flags, ExitCode.Auth, {
7092
7289
  error: "activation timed out before the browser approval arrived",
7093
7290
  code: "entitlement-timeout",
7094
- remediation: "Run `tendril activate` again and complete the browser step within the shown window."
7291
+ remediation: `Run \`${tendrilCommand("activate")}\` again and complete the browser step within the shown window.`
7095
7292
  });
7096
7293
  }
7097
7294
  const parsed = parseEntitlementToken(token);
@@ -7099,7 +7296,7 @@ Waiting for approval\u2026
7099
7296
  fail(flags, ExitCode.Auth, {
7100
7297
  error: `service returned an unusable token: ${parsed.error}`,
7101
7298
  code: "entitlement-invalid",
7102
- remediation: "Retry `tendril activate`; report this if it persists \u2014 it is a service-side fault."
7299
+ remediation: `Retry \`${tendrilCommand("activate")}\`; report this if it persists \u2014 it is a service-side fault.`
7103
7300
  });
7104
7301
  }
7105
7302
  if (Object.keys(PUBLIC_KEYS).length > 0) {
@@ -7114,23 +7311,26 @@ Waiting for approval\u2026
7114
7311
  }
7115
7312
  var ENTITLEMENT_SERVICE_URL;
7116
7313
  var init_activate = __esm({
7117
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/activate.ts"() {
7314
+ "packages/cli/src/commands/activate.ts"() {
7118
7315
  "use strict";
7119
7316
  init_src3();
7120
7317
  init_entitlement();
7318
+ init_invocation();
7121
7319
  init_output();
7122
7320
  ENTITLEMENT_SERVICE_URL = void 0;
7123
7321
  }
7124
7322
  });
7125
7323
 
7126
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/record.ts
7324
+ // packages/cli/src/commands/record.ts
7127
7325
  var record_exports = {};
7128
7326
  __export(record_exports, {
7129
7327
  instanceLeads: () => instanceLeads,
7328
+ interactionDisclosure: () => interactionDisclosure,
7130
7329
  isFigmaAssetUrl: () => isFigmaAssetUrl,
7131
7330
  isLocalAssetUrl: () => isLocalAssetUrl,
7132
7331
  narrowedRoles: () => narrowedRoles,
7133
7332
  nextPayload: () => nextPayload,
7333
+ recordsInteractionState: () => recordsInteractionState,
7134
7334
  runRecordAsset: () => runRecordAsset,
7135
7335
  runRecordFetch: () => runRecordFetch,
7136
7336
  runRecordFinish: () => runRecordFinish,
@@ -7140,10 +7340,30 @@ __export(record_exports, {
7140
7340
  runRecordPlan: () => runRecordPlan,
7141
7341
  runRecordStatus: () => runRecordStatus
7142
7342
  });
7143
- import { existsSync as existsSync19, mkdtempSync as mkdtempSync2, readFileSync as readFileSync16, readdirSync as readdirSync5 } from "node:fs";
7343
+ import { existsSync as existsSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync16, readdirSync as readdirSync5 } from "node:fs";
7144
7344
  import os5 from "node:os";
7145
- import path25 from "node:path";
7345
+ import path26 from "node:path";
7146
7346
  import { writeFileSync as writeFileSync9 } from "node:fs";
7347
+ function recordsInteractionState(reports) {
7348
+ const evident = (s) => stateTokens(s).some((t) => INTERACTION_EVIDENCE_VALUES.has(t));
7349
+ return reports.some((r) => evident(r.axis) || r.domain.some(evident));
7350
+ }
7351
+ function interactionDisclosure(component, reports) {
7352
+ const recordedVariants = reports.map((r) => ({ axis: r.axis, values: r.domain }));
7353
+ const variantSummary = recordedVariants.length === 0 ? "" : recordedVariants.length === 1 ? `${recordedVariants[0].axis} variants for ${andList(recordedVariants[0].values)}` : `variants for ${andList(recordedVariants.map((v) => `${v.axis} (${andList(v.values)})`))}`;
7354
+ const has = recordedVariants.length > 0;
7355
+ const statement = `Your ${component} component ${has ? `has ${variantSummary} \u2014 but none of them is a hover, focus or pressed state` : "has no variants at all, so none of it is a hover, focus or pressed state"}. Nothing in the recording shows how ${component} looks when someone hovers it, tabs to it, or is using it, so Tendril has no picture of those to check the build against. Tendril still builds controls as real, working controls \u2014 never a static lookalike \u2014 and, with no focus state recorded, it uses the browser's own focus indicator rather than inventing a focus ring. You do not need to answer this: recording carries on either way.`;
7356
+ const designFix = `If ${component} should look different when it is focused or hovered, that change belongs in Figma rather than in the code: add a Focus (or Hover) variant to the ${component} component set, record it, and Tendril will match it exactly. If ${component} is not something people interact with, there is nothing to do.`;
7357
+ return {
7358
+ component,
7359
+ recordedVariants,
7360
+ variantSummary,
7361
+ statement,
7362
+ designFix,
7363
+ instruction: "DISCLOSURE, not a question. Say `statement` and `designFix` to a present user at PLAN time, in the SAME message as defaultsToConfirm and every other plan-time question \u2014 one message per session, never one per component and never mid-generation. No answer is required and nothing blocks; proceed with recording whatever they say. State ONLY what `recordedVariants` contains: never infer from the component's NAME what kind of control it is. Non-interactive runs: put the statement in your report.",
7364
+ blocking: false
7365
+ };
7366
+ }
7147
7367
  function symbolsFromMetadataEnvelope(file, sourceFrame) {
7148
7368
  const env = JSON.parse(readFileSync16(file, "utf8"));
7149
7369
  const text = env.content.map((c) => c.text ?? "").join("\n");
@@ -7203,7 +7423,7 @@ function runRecordPlan(opts) {
7203
7423
  if (rawFile !== void 0) {
7204
7424
  try {
7205
7425
  const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
7206
- const tmp = path25.join(mkdtempSync2(path25.join(os5.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
7426
+ const tmp = path26.join(mkdtempSync2(path26.join(os5.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
7207
7427
  writeFileSync9(tmp, JSON.stringify(envelope));
7208
7428
  metadataEntries.push({ file: tmp });
7209
7429
  } catch (err) {
@@ -7225,7 +7445,7 @@ function runRecordPlan(opts) {
7225
7445
  let metadataTruncated = false;
7226
7446
  for (const { file, frame } of metadataEntries) {
7227
7447
  try {
7228
- const parsed = symbolsFromMetadataEnvelope(path25.resolve(file), frame);
7448
+ const parsed = symbolsFromMetadataEnvelope(path26.resolve(file), frame);
7229
7449
  symbols.push(...parsed.symbols);
7230
7450
  if (parsed.truncated) metadataTruncated = true;
7231
7451
  } catch (err) {
@@ -7258,7 +7478,7 @@ function runRecordPlan(opts) {
7258
7478
  if (symbols.length === 0) {
7259
7479
  const leads = metadataEntries.flatMap(({ file }) => {
7260
7480
  try {
7261
- const env = JSON.parse(readFileSync16(path25.resolve(file), "utf8"));
7481
+ const env = JSON.parse(readFileSync16(path26.resolve(file), "utf8"));
7262
7482
  return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
7263
7483
  } catch {
7264
7484
  return [];
@@ -7297,8 +7517,47 @@ function runRecordPlan(opts) {
7297
7517
  const { manifest, plan, resumed, toppedUp } = planSet(opts.setDir, opts.component, symbols, { ...Object.keys(defaults).length > 0 ? { defaults } : {}, ...opts.sample === true ? { sample: true } : {} });
7298
7518
  const defaultReports = reportAxisDefaults(symbols, Object.keys(defaults).length > 0 ? defaults : void 0);
7299
7519
  const toConfirm = resumed ? [] : defaultReports.filter((r) => (r.rule === "non-interaction" || r.rule === "frequency") && r.domain.length > 1);
7300
- const callLow = manifest.reps.length * 3;
7301
- const callHigh = manifest.reps.length * 4;
7520
+ const interactionStatesToConfirm = recordsInteractionState(defaultReports) ? void 0 : interactionDisclosure(manifest.component, defaultReports);
7521
+ const poses = manifest.reps.length;
7522
+ const callLow = poses * FIGMA_CALLS_PER_REP;
7523
+ const callHigh = poses * FIGMA_CALLS_PER_REP_WORST;
7524
+ const posesRemaining = sessionStatus(opts.setDir).reps.filter((r) => r.missing.length > 0).length;
7525
+ const callsNeeded = posesRemaining * FIGMA_CALLS_PER_REP;
7526
+ const callSentence = posesRemaining === poses ? `${poses} poses \xD7 ${FIGMA_CALLS_PER_REP} calls per pose = ${callsNeeded} Figma calls` : `${posesRemaining} poses still to record \xD7 ${FIGMA_CALLS_PER_REP} calls per pose = ${callsNeeded} Figma calls (${poses - posesRemaining} of ${poses} already on disk)`;
7527
+ const arithmetic = `${callSentence} (up to ${posesRemaining * FIGMA_CALLS_PER_REP_WORST} if screenshots or assets need a second fetch)`;
7528
+ const multiDay = callsNeeded > MULTI_DAY_CALL_THRESHOLD;
7529
+ const feasibilityCheck = {
7530
+ arithmetic,
7531
+ callsNeeded,
7532
+ callsWorstCase: posesRemaining * FIGMA_CALLS_PER_REP_WORST,
7533
+ posesToRecord: posesRemaining,
7534
+ callsPerPose: FIGMA_CALLS_PER_REP,
7535
+ instruction: "BEFORE the first recording call, call the Figma MCP `whoami` tool \u2014 it names the seat and plan and is EXEMPT from Figma's tool-call limits, so this check costs nothing and spends no quota. Read the daily tool-call allowance for that seat/plan and divide: callsNeeded \xF7 allowance = days this set needs. Tendril owns the multiplication above; the allowance is Figma's and is theirs to change, so take it from whoami and never from memory.",
7536
+ surface: multiDay ? "This set exceeds every daily allowance we have heard of, so it CANNOT finish in one day. State the verdict to the user in plain numbers BEFORE recording anything and let them choose from `options` \u2014 do not start on the assumption it will fit." : "Say nothing about cost if the division fits inside one day's allowance \u2014 a set that comfortably fits is not worth the user's attention. Surface the verdict and `options` only when it does not fit.",
7537
+ verdictTemplate: `${callSentence}. Your <seat> seat on <plan> allows <allowance>/day. This set needs about <days> day(s) at that rate.`,
7538
+ options: [
7539
+ {
7540
+ id: "pace",
7541
+ decidedBy: "user, and you can carry it out",
7542
+ text: "Spread the recording across days, staying inside the daily allowance. Nothing is lost at a limit: the set persists after every pose and re-running the same command resumes from disk, so a rate limit costs waiting, never recorded work."
7543
+ },
7544
+ {
7545
+ id: "sample",
7546
+ // Sampling trades away lattice coverage, so it stays a human
7547
+ // decision structurally, not by instruction: it exists only as
7548
+ // a CLI flag and the MCP surface has no parameter for it.
7549
+ decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
7550
+ 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.",
7551
+ userRuns: [`rm ${path26.join(opts.setDir, "recording-set.json")}`, `${tendrilCommand(`record plan --set ${opts.setDir} --component ${manifest.component}`)} --sample`]
7552
+ },
7553
+ {
7554
+ id: "larger-allowance",
7555
+ decidedBy: "user",
7556
+ text: "A seat or plan with a larger daily allowance, if their organisation allows it. Figma's terms, not ours \u2014 state it as an option, never as advice to spend money."
7557
+ }
7558
+ ],
7559
+ limitHintIfWhoamiIsSilent: LIMIT_HINT
7560
+ };
7302
7561
  emitData(
7303
7562
  opts,
7304
7563
  {
@@ -7314,11 +7573,19 @@ function runRecordPlan(opts) {
7314
7573
  ...metadataTruncated ? { metadataTruncated: true } : {},
7315
7574
  ...toppedUp !== void 0 ? { toppedUp } : {},
7316
7575
  notRecorded: manifest.notRecorded ?? null,
7317
- figmaCallEstimate: { reps: manifest.reps.length, calls: `~${callLow}\u2013${callHigh}` },
7576
+ // The whole-set ledger, numbers only — what recording this queue
7577
+ // from nothing costs. What it costs FROM HERE, and what to do
7578
+ // about it, is feasibilityCheck's job.
7579
+ figmaCallEstimate: { reps: poses, callsPerRep: FIGMA_CALLS_PER_REP, calls: `~${callLow}\u2013${callHigh}`, callsMin: callLow, callsMax: callHigh },
7580
+ // A sibling of defaultsToConfirm: a handshake the agent completes
7581
+ // with the user before the queue costs anything, not a field to
7582
+ // relay as duration.
7583
+ feasibilityCheck,
7318
7584
  // Cold-start fix (run 7): the first instruction rides the plan
7319
7585
  // response, so record_next is never needed to begin — it exists
7320
7586
  // only for resuming.
7321
7587
  next: nextPayload(opts.setDir),
7588
+ ...interactionStatesToConfirm !== void 0 ? { interactionStatesToConfirm } : {},
7322
7589
  ...toConfirm.length > 0 ? {
7323
7590
  defaultsToConfirm: {
7324
7591
  instruction: "HEURISTIC defaults: the designer named no literal Default and no override was given, so the code guessed the component's zero point \u2014 the pose an empty-props mount shows and the pose behaviour checks aim at. When a USER is present, ask each question below BEFORE recording; if they pick a different value, re-run this exact plan command with --default <Axis>=<Value> (allowed until the first envelope is ingested; frozen with the recording after). Non-interactive: proceed with the resolved values and state them in your report.",
@@ -7331,6 +7598,42 @@ function runRecordPlan(opts) {
7331
7598
  } : {}
7332
7599
  },
7333
7600
  () => {
7601
+ const writeFeasibility = () => {
7602
+ if (callsNeeded === 0) return;
7603
+ process.stdout.write(`recording cost: ${arithmetic}
7604
+ `);
7605
+ if (multiDay) {
7606
+ process.stdout.write(`FEASIBILITY: ${callsNeeded} calls exceeds every daily Figma allowance we have heard of \u2014 this CANNOT finish in one day.
7607
+ `);
7608
+ process.stdout.write(` ask Figma whoami (free \u2014 exempt from tool-call limits) for this seat's daily allowance, divide ${callsNeeded} by it, and say how many DAYS this needs before recording
7609
+ `);
7610
+ process.stdout.write(` (a) pace it across days \u2014 the set persists after every pose, so a rate limit costs waiting, never recorded work
7611
+ `);
7612
+ process.stdout.write(` (b) record fewer poses \u2014 YOUR call, not an agent's: delete recording-set.json and re-plan with --sample (buys calls with coverage)
7613
+ `);
7614
+ process.stdout.write(` (c) a seat or plan with a larger daily allowance
7615
+ `);
7616
+ } else {
7617
+ process.stdout.write(`FEASIBILITY: check ${callsNeeded} against this seat's daily Figma allowance (ask whoami \u2014 it is free) before recording; it only needs raising if it does not fit
7618
+ `);
7619
+ }
7620
+ };
7621
+ const writeInteraction = () => {
7622
+ if (interactionStatesToConfirm === void 0) return;
7623
+ const d = interactionStatesToConfirm;
7624
+ process.stdout.write(
7625
+ `CONFIRM states: ${d.component} records no hover, focus or pressed state${d.recordedVariants.length > 0 ? ` \u2014 it has ${d.variantSummary}` : " (it has no variants at all)"}
7626
+ `
7627
+ );
7628
+ process.stdout.write(` nothing in the recording shows how it looks when someone hovers it, tabs to it, or is using it
7629
+ `);
7630
+ process.stdout.write(` Tendril still builds controls as real controls \u2014 never a static lookalike \u2014 and uses the browser's own focus indicator instead of inventing a focus ring
7631
+ `);
7632
+ process.stdout.write(` want a focus ring of your own? add a Focus variant to the ${d.component} component set in Figma and record again \u2014 Tendril will match it exactly
7633
+ `);
7634
+ process.stdout.write(` nothing to answer \u2014 recording carries on either way
7635
+ `);
7636
+ };
7334
7637
  if (resumed) {
7335
7638
  if (opts.sample === true && (manifest.planMode ?? "sample") === "full") {
7336
7639
  process.stdout.write("NOTE: --sample has no effect on a resumed full-matrix set \u2014 delete recording-set.json to re-plan sampled (partial coverage is a deliberate choice)\n");
@@ -7344,18 +7647,20 @@ function runRecordPlan(opts) {
7344
7647
  process.stdout.write(`resumed existing plan (${manifest.reps.length} reps, ${manifest.planMode ?? "sample"} mode) \u2014 delete recording-set.json to re-plan
7345
7648
  `);
7346
7649
  }
7650
+ writeFeasibility();
7651
+ writeInteraction();
7347
7652
  return;
7348
7653
  }
7349
7654
  for (const r of plan.reps) process.stdout.write(`planned ${r.slug.padEnd(24)} ${r.nodeId} (${r.tier})
7350
7655
  `);
7351
7656
  if (plan.notRecorded.length > 0) process.stdout.write(`not recorded (${plan.notRecorded.length}): disclosed in the manifest
7352
7657
  `);
7353
- process.stdout.write(`estimated recording cost: ~${callLow}\u2013${callHigh} Figma calls for ${manifest.reps.length} reps
7354
- `);
7658
+ writeFeasibility();
7355
7659
  for (const q2 of toConfirm) {
7356
7660
  process.stdout.write(`CONFIRM ${q2.axis}: default resolved to "${q2.value}" by heuristic (${q2.rule}) \u2014 ask the user; change with --default before recording
7357
7661
  `);
7358
7662
  }
7663
+ writeInteraction();
7359
7664
  }
7360
7665
  );
7361
7666
  }
@@ -7363,7 +7668,7 @@ function nextPayload(setDir) {
7363
7668
  const instruction = nextInstruction(setDir);
7364
7669
  const status = sessionStatus(setDir);
7365
7670
  const progress = { recordedReps: status.reps.filter((x) => x.missing.length === 0).length, totalReps: status.reps.length };
7366
- if (instruction === null && !existsSync19(path25.join(setDir, "get_variable_defs.json"))) {
7671
+ if (instruction === null && !existsSync20(path26.join(setDir, "get_variable_defs.json"))) {
7367
7672
  const manifest = loadManifest(setDir);
7368
7673
  const frameNode = Object.keys(manifest.sourceFrames ?? {})[0] ?? manifest.reps[0]?.nodeId ?? "";
7369
7674
  return { slug: "__set__", nodeId: frameNode, tool: "get_variable_defs", note: `SET-LEVEL: call get_variable_defs on the component frame and ingest with --rep __set__. ${ENVELOPE_HELP}`, progress };
@@ -7380,7 +7685,7 @@ function runRecordNext(opts) {
7380
7685
  const progress = payload["progress"];
7381
7686
  process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
7382
7687
  \u2192 ${payload["note"]}
7383
- \u2192 then: tendril record ingest --set <dir> --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>
7688
+ \u2192 then: ${tendrilCommand(`record ingest --set ${path26.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
7384
7689
  `);
7385
7690
  });
7386
7691
  }
@@ -7454,7 +7759,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
7454
7759
  const skipped = [];
7455
7760
  const failed = [];
7456
7761
  for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
7457
- if (existsSync19(path25.join(setDir, rep, name))) {
7762
+ if (existsSync20(path26.join(setDir, rep, name))) {
7458
7763
  skipped.push(name);
7459
7764
  continue;
7460
7765
  }
@@ -7476,16 +7781,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
7476
7781
  }
7477
7782
  function rawEnvelopeFromFile(file, parts) {
7478
7783
  if (parts) {
7479
- const blocks = JSON.parse(readFileSync16(path25.resolve(file), "utf8"));
7784
+ const blocks = JSON.parse(readFileSync16(path26.resolve(file), "utf8"));
7480
7785
  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");
7481
7786
  return { content: blocks.map((text) => ({ type: "text", text })) };
7482
7787
  }
7483
- return { content: [{ type: "text", text: readFileSync16(path25.resolve(file), "utf8") }] };
7788
+ return { content: [{ type: "text", text: readFileSync16(path26.resolve(file), "utf8") }] };
7484
7789
  }
7485
7790
  async function runRecordIngest(opts) {
7486
7791
  let payload;
7487
7792
  try {
7488
- payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync16(path25.resolve(opts.file), "utf8"));
7793
+ payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync16(path26.resolve(opts.file), "utf8"));
7489
7794
  } catch (err) {
7490
7795
  fail(opts, ExitCode.InputValidation, {
7491
7796
  error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
@@ -7497,7 +7802,7 @@ async function runRecordIngest(opts) {
7497
7802
  fail(opts, ExitCode.InputValidation, {
7498
7803
  error: "--raw is for text tool responses; screenshots are binary",
7499
7804
  code: "envelope-invalid",
7500
- remediation: "Use `tendril record fetch` with the image_url instead."
7805
+ remediation: `Use \`${tendrilCommand(`record fetch --set ${path26.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
7501
7806
  });
7502
7807
  }
7503
7808
  if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
@@ -7509,7 +7814,15 @@ async function runRecordIngest(opts) {
7509
7814
  remediation: "Save the get_variable_defs response verbatim as a text envelope."
7510
7815
  });
7511
7816
  }
7512
- writeFileSync9(path25.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
7817
+ const content = checkEnvelopeContent("get_variable_defs", payload);
7818
+ if (!content.ok) {
7819
+ fail(opts, ExitCode.InputValidation, {
7820
+ error: `set-level get_variable_defs was NOT recorded \u2014 ${content.reason}`,
7821
+ code: "envelope-rejected",
7822
+ remediation: REINGEST_GUIDANCE
7823
+ });
7824
+ }
7825
+ writeFileSync9(path26.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
7513
7826
  `);
7514
7827
  emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
7515
7828
  process.stdout.write("set-level get_variable_defs ingested\n");
@@ -7525,7 +7838,7 @@ async function runRecordIngest(opts) {
7525
7838
  if (assets !== void 0) {
7526
7839
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
7527
7840
  `);
7528
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it and run tendril record asset
7841
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path26.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
7529
7842
  `);
7530
7843
  }
7531
7844
  });
@@ -7533,7 +7846,7 @@ async function runRecordIngest(opts) {
7533
7846
  fail(opts, ExitCode.InputValidation, {
7534
7847
  error: err instanceof Error ? err.message : String(err),
7535
7848
  code: "envelope-rejected",
7536
- remediation: "The envelope failed boundary validation \u2014 record the tool response again, verbatim, and re-ingest."
7849
+ remediation: "Fix what the error names, then re-run this exact command \u2014 recording resumes from disk, so nothing already recorded is lost."
7537
7850
  });
7538
7851
  }
7539
7852
  }
@@ -7598,14 +7911,14 @@ async function runRecordIngestRep(opts) {
7598
7911
  if (assets !== void 0) {
7599
7912
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
7600
7913
  `);
7601
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it and run tendril record asset
7914
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path26.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
7602
7915
  `);
7603
7916
  }
7604
7917
  });
7605
7918
  }
7606
7919
  function runRecordAsset(opts) {
7607
7920
  if (opts.dir !== void 0) {
7608
- const dir = path25.resolve(opts.dir);
7921
+ const dir = path26.resolve(opts.dir);
7609
7922
  const names = readdirSync5(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
7610
7923
  if (names.length === 0) {
7611
7924
  fail(opts, ExitCode.InputValidation, {
@@ -7617,7 +7930,7 @@ function runRecordAsset(opts) {
7617
7930
  const ingested = [];
7618
7931
  try {
7619
7932
  for (const name of names) {
7620
- ingestAsset(opts.setDir, opts.rep, name, readFileSync16(path25.join(dir, name)));
7933
+ ingestAsset(opts.setDir, opts.rep, name, readFileSync16(path26.join(dir, name)));
7621
7934
  ingested.push(name);
7622
7935
  }
7623
7936
  } catch (err) {
@@ -7637,11 +7950,11 @@ function runRecordAsset(opts) {
7637
7950
  fail(opts, ExitCode.InputValidation, {
7638
7951
  error: "pass --name and --file for a single asset, or --dir for a batch",
7639
7952
  code: "asset-rejected",
7640
- remediation: "tendril record asset --set <dir> --rep <slug> --dir <downloads-dir>"
7953
+ remediation: tendrilCommand(`record asset --set ${path26.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
7641
7954
  });
7642
7955
  }
7643
7956
  try {
7644
- ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync16(path25.resolve(opts.file)));
7957
+ ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync16(path26.resolve(opts.file)));
7645
7958
  emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
7646
7959
  process.stdout.write(`ingested ${opts.rep}/${opts.name}
7647
7960
  `);
@@ -7677,7 +7990,7 @@ function narrowedRoles(derived, override) {
7677
7990
  function rolesFromFile(opts, file, derived) {
7678
7991
  let json;
7679
7992
  try {
7680
- json = JSON.parse(readFileSync16(path25.resolve(file), "utf8"));
7993
+ json = JSON.parse(readFileSync16(path26.resolve(file), "utf8"));
7681
7994
  } catch (err) {
7682
7995
  fail(opts, ExitCode.InputValidation, {
7683
7996
  error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -7715,11 +8028,11 @@ function rolesFromFile(opts, file, derived) {
7715
8028
  };
7716
8029
  }
7717
8030
  function runRecordFinish(opts) {
7718
- if (!existsSync19(path25.join(opts.setDir, "recording-set.json"))) {
8031
+ if (!existsSync20(path26.join(opts.setDir, "recording-set.json"))) {
7719
8032
  fail(opts, ExitCode.InputValidation, {
7720
8033
  error: `no recording-set.json in ${opts.setDir}`,
7721
8034
  code: "no-recording-set",
7722
- remediation: `Run \`tendril record plan --set ${opts.setDir} --component <name> --metadata <envelope.json>\` first \u2014 \`record finish\` closes a set the planner opened.`
8035
+ remediation: `Run \`${tendrilCommand(`record plan --set ${path26.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
7723
8036
  });
7724
8037
  }
7725
8038
  const { manifest, raw } = readManifestFile(opts.setDir);
@@ -7747,17 +8060,17 @@ function runRecordFinish(opts) {
7747
8060
  fail(opts, ExitCode.ConfirmationRequired, {
7748
8061
  error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
7749
8062
  code: "roles-confirmation-not-interactive",
7750
- remediation: "A human runs `tendril record finish --set <dir> --confirm-roles` in their own terminal. Agents: show the proposal to your operator instead of confirming it."
8063
+ remediation: `A human runs \`${tendrilCommand(`record finish --set ${path26.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
7751
8064
  });
7752
8065
  }
7753
8066
  const merged = { ...raw, roles };
7754
- const { issues } = validateRecordingSet(merged, (rel) => existsSync19(path25.join(opts.setDir, rel)));
8067
+ const { issues } = validateRecordingSet(merged, (rel) => existsSync20(path26.join(opts.setDir, rel)));
7755
8068
  const errors = issues.filter((i) => i.severity === "error");
7756
8069
  if (errors.length > 0) {
7757
8070
  fail(opts, ExitCode.InputValidation, {
7758
8071
  error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
7759
8072
  code: "recording-set-invalid",
7760
- remediation: "Fix the set (`tendril record status --set <dir>` lists missing envelopes) or the roles graph, then re-run `record finish`."
8073
+ remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path26.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
7761
8074
  });
7762
8075
  }
7763
8076
  for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
@@ -7768,29 +8081,49 @@ function runRecordFinish(opts) {
7768
8081
  `);
7769
8082
  }
7770
8083
  }
7771
- var ENVELOPE_HELP, isAutoFetchAssetUrl, describeRoleLoss;
8084
+ var FIGMA_CALLS_PER_REP, FIGMA_CALLS_PER_REP_WORST, MULTI_DAY_CALL_THRESHOLD, LIMIT_HINT, stateTokens, MAX_SPOKEN_VALUES, andList, ENVELOPE_HELP, isAutoFetchAssetUrl, describeRoleLoss;
7772
8085
  var init_record = __esm({
7773
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/record.ts"() {
8086
+ "packages/cli/src/commands/record.ts"() {
7774
8087
  "use strict";
7775
8088
  init_src3();
7776
8089
  init_src();
7777
8090
  init_src6();
7778
8091
  init_output();
7779
8092
  init_entitlement();
7780
- ENVELOPE_HELP = 'Envelope format: text tools save {"content":[{"type":"text","text":"<VERBATIM response text incl. any Currently-selected-nodes block>"}]}; get_screenshot: do NOT download the image yourself \u2014 pass its image_url to `tendril record fetch` (MCP: tendril_record_fetch), which pulls the bytes to disk directly. That keeps the pixel ground truth out of your context and costs one approval instead of a shell command per asset. Assets are asset-<first-8-hex-of-figma-uuid>.<ext>; their URLs appear as const declarations inside the design-context text and also expire.';
8093
+ init_invocation();
8094
+ FIGMA_CALLS_PER_REP = 3;
8095
+ FIGMA_CALLS_PER_REP_WORST = 4;
8096
+ MULTI_DAY_CALL_THRESHOLD = 600;
8097
+ LIMIT_HINT = {
8098
+ status: "UNVERIFIED \u2014 recorded 2026-08-14 from Figma's published documentation and one field report; Figma changes these at will.",
8099
+ use: "Use ONLY if whoami states no allowance, and say it is unverified when you show it. The allowance whoami reports always wins.",
8100
+ perDayByPlan: {
8101
+ "Dev/Full seat, Professional": "~200 tool calls/day",
8102
+ "Dev/Full seat, Organization or Enterprise": "~600 tool calls/day",
8103
+ Starter: "~6 tool calls/month"
8104
+ },
8105
+ burst: "A per-minute ceiling is documented alongside the daily one, so parallel recorders can trip a limit long before the daily budget runs out."
8106
+ };
8107
+ stateTokens = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").split("-").filter((t) => t !== "");
8108
+ MAX_SPOKEN_VALUES = 6;
8109
+ andList = (items) => {
8110
+ const shown = items.length <= MAX_SPOKEN_VALUES ? items : [...items.slice(0, MAX_SPOKEN_VALUES), `${items.length - MAX_SPOKEN_VALUES} more`];
8111
+ return shown.length <= 1 ? shown[0] ?? "" : `${shown.slice(0, -1).join(", ")} and ${shown[shown.length - 1]}`;
8112
+ };
8113
+ ENVELOPE_HELP = `Envelope format: text tools save {"content":[{"type":"text","text":"<VERBATIM response text incl. any Currently-selected-nodes block>"}]}; get_screenshot: do NOT download the image yourself \u2014 pass its image_url to \`${tendrilCommand("record fetch")}\` (MCP: tendril_record_fetch), which pulls the bytes to disk directly. That keeps the pixel ground truth out of your context and costs one approval instead of a shell command per asset. Assets are asset-<first-8-hex-of-figma-uuid>.<ext>; their URLs appear as const declarations inside the design-context text and also expire.`;
7781
8114
  isAutoFetchAssetUrl = (url) => isFigmaAssetUrl(url) || isLocalAssetUrl(url);
7782
8115
  describeRoleLoss = (loss) => loss.kind === "main" ? `drops main "${loss.main}"` : `stops "${loss.part}" being a part of main "${loss.main}"`;
7783
8116
  }
7784
8117
  });
7785
8118
 
7786
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/engine.ts
8119
+ // packages/generate/src/engine.ts
7787
8120
  var init_engine = __esm({
7788
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/engine.ts"() {
8121
+ "packages/generate/src/engine.ts"() {
7789
8122
  "use strict";
7790
8123
  }
7791
8124
  });
7792
8125
 
7793
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/parse.ts
8126
+ // packages/generate/src/parse.ts
7794
8127
  function parseFiles(text, entry) {
7795
8128
  const out = /* @__PURE__ */ new Map();
7796
8129
  const re = /```[a-z]*[^\n]*\n(?:\/\/|\/\*)[ \t]*FILE:[ \t]*([\w.]+)[ \t]*(?:\*\/)?[ \t]*\n([\s\S]*?)```/g;
@@ -7823,15 +8156,15 @@ ${content}\`\`\``);
7823
8156
  return blocks.join("\n");
7824
8157
  }
7825
8158
  var init_parse = __esm({
7826
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/parse.ts"() {
8159
+ "packages/generate/src/parse.ts"() {
7827
8160
  "use strict";
7828
8161
  }
7829
8162
  });
7830
8163
 
7831
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/engine-curated.ts
8164
+ // packages/generate/src/engine-curated.ts
7832
8165
  var CuratedEngine;
7833
8166
  var init_engine_curated = __esm({
7834
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/engine-curated.ts"() {
8167
+ "packages/generate/src/engine-curated.ts"() {
7835
8168
  "use strict";
7836
8169
  init_parse();
7837
8170
  CuratedEngine = class {
@@ -7969,9 +8302,9 @@ var init_engine_curated = __esm({
7969
8302
  }
7970
8303
  });
7971
8304
 
7972
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/loop.ts
7973
- import { existsSync as existsSync20, mkdirSync as mkdirSync6, readFileSync as readFileSync17, renameSync, writeFileSync as writeFileSync10 } from "node:fs";
7974
- import path26 from "node:path";
8305
+ // packages/generate/src/loop.ts
8306
+ import { existsSync as existsSync21, mkdirSync as mkdirSync6, readFileSync as readFileSync17, renameSync, writeFileSync as writeFileSync10 } from "node:fs";
8307
+ import path27 from "node:path";
7975
8308
  import { z as z11 } from "zod";
7976
8309
  function objective(scores, behaviors) {
7977
8310
  const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
@@ -8005,12 +8338,12 @@ ${preludeLines.join("\n")}` : ""}${absentLines.length > 0 ? `
8005
8338
  MISSING FEATURES (absent-ink clusters \u2014 recorded marks your render leaves out or paints invisibly; GATING at the cert bar. Fix the named node's ink \u2014 a config carrying one cannot certify):
8006
8339
  ${absentLines.join("\n")}` : ""}
8007
8340
 
8008
- 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=1 with LOW sim means nothing is missing \u2014 shapes or positions are wrong, do not chase missing ink; 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."}`;
8341
+ 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."}`;
8009
8342
  }
8010
8343
  function archivePriorRun(outDir) {
8011
- if (!existsSync20(path26.join(outDir, "run-log.json")) && !existsSync20(path26.join(outDir, "loop-state.json"))) return void 0;
8344
+ if (!existsSync21(path27.join(outDir, "run-log.json")) && !existsSync21(path27.join(outDir, "loop-state.json"))) return void 0;
8012
8345
  let n = 1;
8013
- while (existsSync20(`${outDir}-prev-${n}`)) n += 1;
8346
+ while (existsSync21(`${outDir}-prev-${n}`)) n += 1;
8014
8347
  renameSync(outDir, `${outDir}-prev-${n}`);
8015
8348
  return `${outDir}-prev-${n}`;
8016
8349
  }
@@ -8019,14 +8352,14 @@ async function runEngineLoop(opts) {
8019
8352
  const plateau = opts.plateau ?? 2;
8020
8353
  const progress = opts.onProgress ?? (() => {
8021
8354
  });
8022
- const statePath = path26.join(opts.outDir, "loop-state.json");
8023
- const resuming = opts.resume === true && existsSync20(statePath);
8355
+ const statePath = path27.join(opts.outDir, "loop-state.json");
8356
+ const resuming = opts.resume === true && existsSync21(statePath);
8024
8357
  if (!resuming) {
8025
8358
  const archived = archivePriorRun(opts.outDir);
8026
8359
  if (archived !== void 0) progress(`previous run archived to ${archived}`);
8027
8360
  }
8028
8361
  mkdirSync6(opts.outDir, { recursive: true });
8029
- const scratch = path26.join(opts.outDir, ".candidate");
8362
+ const scratch = path27.join(opts.outDir, ".candidate");
8030
8363
  let attempts = [];
8031
8364
  let log = [];
8032
8365
  let best;
@@ -8054,7 +8387,7 @@ async function runEngineLoop(opts) {
8054
8387
  };
8055
8388
  const writeCandidate = (files) => {
8056
8389
  mkdirSync6(scratch, { recursive: true });
8057
- for (const [name, content] of Object.entries(files)) writeFileSync10(path26.join(scratch, name), content);
8390
+ for (const [name, content] of Object.entries(files)) writeFileSync10(path27.join(scratch, name), content);
8058
8391
  };
8059
8392
  const scoreCandidate = async (candidate, iter, usd, modelMs) => {
8060
8393
  writeCandidate(candidate.files);
@@ -8112,8 +8445,8 @@ async function runEngineLoop(opts) {
8112
8445
  const usd = candidate.usage?.usd ?? 0;
8113
8446
  spentUsd += usd;
8114
8447
  if (candidate.raw !== void 0) {
8115
- mkdirSync6(path26.join(opts.outDir, "responses"), { recursive: true });
8116
- writeFileSync10(path26.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
8448
+ mkdirSync6(path27.join(opts.outDir, "responses"), { recursive: true });
8449
+ writeFileSync10(path27.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
8117
8450
  }
8118
8451
  if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
8119
8452
  const finish = candidate.usage?.finishReason ?? "?";
@@ -8139,10 +8472,10 @@ async function runEngineLoop(opts) {
8139
8472
  }
8140
8473
  }
8141
8474
  }
8142
- if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(path26.join(opts.outDir, name), content);
8475
+ if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(path27.join(opts.outDir, name), content);
8143
8476
  const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
8144
8477
  writeFileSync10(
8145
- path26.join(opts.outDir, "run-log.json"),
8478
+ path27.join(opts.outDir, "run-log.json"),
8146
8479
  `${JSON.stringify(
8147
8480
  {
8148
8481
  ...opts.meta,
@@ -8163,7 +8496,7 @@ async function runEngineLoop(opts) {
8163
8496
  }
8164
8497
  var better, RegionSchema, ConfigScoreSchema, BehaviorResultSchema, LoopStateSchema;
8165
8498
  var init_loop2 = __esm({
8166
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/loop.ts"() {
8499
+ "packages/generate/src/loop.ts"() {
8167
8500
  "use strict";
8168
8501
  better = (a, b) => a[0] !== b[0] ? a[0] > b[0] : a[1] !== b[1] ? a[1] > b[1] : a[2] > b[2];
8169
8502
  RegionSchema = z11.object({ x0: z11.number(), y0: z11.number(), x1: z11.number(), y1: z11.number(), density: z11.number() });
@@ -8208,9 +8541,9 @@ var init_loop2 = __esm({
8208
8541
  }
8209
8542
  });
8210
8543
 
8211
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/brief.ts
8212
- import { existsSync as existsSync21, readFileSync as readFileSync18 } from "node:fs";
8213
- import path27 from "node:path";
8544
+ // packages/generate/src/brief.ts
8545
+ import { existsSync as existsSync22, readFileSync as readFileSync18 } from "node:fs";
8546
+ import path28 from "node:path";
8214
8547
  function singleAxes2(name) {
8215
8548
  const parsed = parseVariantAxes(name);
8216
8549
  if (parsed === void 0) return void 0;
@@ -8369,7 +8702,7 @@ ${propLines.join("\n")}
8369
8702
 
8370
8703
  ${syntheticCombos.length > 0 ? `UNRECORDED REACHABLE COMBINATIONS: the design's exclusive axis cannot express ${syntheticCombos.join(", ")} \u2014 the API split makes them reachable with NO recorded truth. Compose them from the recorded per-axis truth (paint variables: one axis sets, the other consumes), never invent a bespoke look, and list them in your report.
8371
8704
  ` : ""}${slots.length > 0 ? `CONTENT PROPS: every string prop defaults to its RECORDED text \u2014 render the PROP, never a hardcoded literal. Configs pass per-pose recorded strings wherever the recording varies, and pixels enforce them: a hardcoded string fails those configs. Content presence (a heading that only exists in some poses) follows the AXIS props; the string prop only supplies the text. RICH-TEXT DEFAULTS: when the recorded content is a multi-run rich node (mixed weights, an underlined link span) whose concatenation is the prop's default string, a plain-string default would flatten the recorded formatting \u2014 default the parameter to undefined and render the recorded rich markup when the prop is absent; a caller-supplied string then renders plainly. That mirrors the design tool's own emission logic and keeps every recorded pose pixel-exact.
8372
- ` : ""}Rules: generated element ids come from React's useId() \u2014 never Math.random() in render/state init (SSR hydration hazard; measured: two same-day bundles disagreed). Plain CSS in styles.css (tokens.css optional, loaded first) \u2014 no Tailwind, no imports beyond react/react-dom, and NEVER import the stylesheet from the entry module: the harness injects tokens.css and styles.css itself, and an entry that imports CSS does not compile here (measured cost: one full round, every config 0). The component renders the RECORDED content as its defaults \u2014 reproduce it from the emissions. ${forcingCanon}${fontsLine}The host sizes nothing: the component is its natural recorded size. FLUIDITY AFFORDANCE (emit it always): every ROOT width declaration rides width: var(--tendril-root-width, <recorded>px) with that pose's recorded width as the fallback \u2014 per-variant root widths reuse the SAME property name with their own recorded fallbacks. With the property unset this is byte-equivalent truth: identical pixels, identical operability measurement (measured: flipping roots to a bare 100% held 20/20 pixels but made a commit check unmeasurable \u2014 the recorded pin carries real signal). A consumer makes an instance fluid by setting --tendril-root-width (e.g. 100%) on a wrapper, no edit to certified CSS. Widths only: recorded heights and internal geometry stay literal \u2014 fixed heights are often genuine design intent.`;
8705
+ ` : ""}Rules: generated element ids come from React's useId() \u2014 never Math.random() in render/state init (SSR hydration hazard; measured: two same-day bundles disagreed). Plain CSS in styles.css (tokens.css optional, loaded first) \u2014 no Tailwind, no imports beyond react/react-dom, and NEVER import the stylesheet from the entry module: the harness injects tokens.css and styles.css itself, and an entry that imports CSS does not compile here (measured cost: one full round, every config 0). The component renders the RECORDED content as its defaults \u2014 reproduce it from the emissions. ${forcingCanon}${fontsLine}ROOT SIZE IS YOURS AND PIXELS DO NOT CHECK IT, IN EITHER DIRECTION: the mount floors your root to the recorded box (#root > *{min-width:<recorded w>px;min-height:<recorded h>px} \u2014 a floor, never a cap), and the image the scorer compares is a CROP of that box, so paint outside it is never compared at all. Measured through the real scorer against a correct 120x40 root: an UNDERSIZED 80x28 root and OVERSIZED 160x52 and 124x44 roots ALL scored sim 1.000 / ink 1.000 with a byte-identical crop, as long as internals stay literal and top-left anchored. An oversized root shows only when its own border, radius or shadow \u2014 or a re-centred internal layout \u2014 lands back inside the crop. And some roots are not floored at all: a non-replaced INLINE root (a bare <label>, or an <a href> for a link variant) ignores width/height/min-width/min-height entirely and renders content-sized whatever you declare. So take both root dimensions from the recorded box in the payload, and give the root a display that honours them \u2014 no score, passing or failing, is evidence they are right. FLUIDITY AFFORDANCE (emit it always): every ROOT width declaration rides width: var(--tendril-root-width, <recorded>px) with that pose's recorded width as the fallback \u2014 per-variant root widths reuse the SAME property name with their own recorded fallbacks. With the property unset this is byte-equivalent truth: identical pixels, identical operability measurement (measured: flipping roots to a bare 100% held 20/20 pixels but made a commit check unmeasurable \u2014 the recorded pin carries real signal). A consumer makes an instance fluid by setting --tendril-root-width (e.g. 100%) on a wrapper, no edit to certified CSS. Widths only: recorded heights and internal geometry stay literal \u2014 fixed heights are often genuine design intent.`;
8373
8706
  const mappedTokens = /* @__PURE__ */ new Set([...forcedStates, ...props.filter((p) => p.kind === "boolean").map((p) => p.name)]);
8374
8707
  const unmappedInteractionEvidence = interactionEvidence.filter((e) => {
8375
8708
  const sel = e.endsWith(" (selection axis)");
@@ -8449,8 +8782,8 @@ function envelopeText(file) {
8449
8782
  }
8450
8783
  function dismissEvidence(setDir, repSlugs) {
8451
8784
  for (const slug of repSlugs) {
8452
- const f = path27.join(setDir, slug, "get_design_context.json");
8453
- if (!existsSync21(f)) continue;
8785
+ const f = path28.join(setDir, slug, "get_design_context.json");
8786
+ if (!existsSync22(f)) continue;
8454
8787
  const text = envelopeText(f);
8455
8788
  const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*(?:true|false)\b/i.exec(text) ?? /\b(\w*dismiss\w*)\??\s*:\s*boolean\b/i.exec(text);
8456
8789
  if (propHit !== null) return `emission prop "${propHit[1]}"`;
@@ -8494,13 +8827,13 @@ function recordedFontNeeds(setDir, opts = {}) {
8494
8827
  }
8495
8828
  };
8496
8829
  const manifest = loadManifest(setDir);
8497
- const setDefs = path27.join(setDir, "get_variable_defs.json");
8498
- if (existsSync21(setDefs)) fromDefs(envelopeText(setDefs));
8830
+ const setDefs = path28.join(setDir, "get_variable_defs.json");
8831
+ if (existsSync22(setDefs)) fromDefs(envelopeText(setDefs));
8499
8832
  for (const rep of manifest.reps) {
8500
- const ctx = path27.join(setDir, rep.slug, "get_design_context.json");
8501
- if (existsSync21(ctx)) fromEmission(envelopeText(ctx));
8502
- const defs = path27.join(setDir, rep.slug, "get_variable_defs.json");
8503
- if (existsSync21(defs)) fromDefs(envelopeText(defs));
8833
+ const ctx = path28.join(setDir, rep.slug, "get_design_context.json");
8834
+ if (existsSync22(ctx)) fromEmission(envelopeText(ctx));
8835
+ const defs = path28.join(setDir, rep.slug, "get_variable_defs.json");
8836
+ if (existsSync22(defs)) fromDefs(envelopeText(defs));
8504
8837
  }
8505
8838
  return [...byFamily.entries()].map(([family, paired]) => {
8506
8839
  const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
@@ -8511,8 +8844,8 @@ function symbolFontGlyphCount(setDir, reps) {
8511
8844
  const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
8512
8845
  const glyphs = /* @__PURE__ */ new Set();
8513
8846
  for (const rep of reps) {
8514
- const file = path27.join(setDir, rep, "get_metadata.json");
8515
- if (!existsSync21(file)) continue;
8847
+ const file = path28.join(setDir, rep, "get_metadata.json");
8848
+ if (!existsSync22(file)) continue;
8516
8849
  try {
8517
8850
  const text = JSON.parse(readFileSync18(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
8518
8851
  for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
@@ -8540,8 +8873,8 @@ function recordedTextSlots(setDir, repSlugs) {
8540
8873
  const propRep = [];
8541
8874
  const perRep = [];
8542
8875
  for (const slug of repSlugs) {
8543
- const f = path27.join(setDir, slug, "get_design_context.json");
8544
- if (!existsSync21(f)) continue;
8876
+ const f = path28.join(setDir, slug, "get_design_context.json");
8877
+ if (!existsSync22(f)) continue;
8545
8878
  const code = envelopeText(f);
8546
8879
  const props = /* @__PURE__ */ new Map();
8547
8880
  for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
@@ -8566,8 +8899,8 @@ function recordedTextSlots(setDir, repSlugs) {
8566
8899
  }
8567
8900
  const axisValuesBySlug = /* @__PURE__ */ new Map();
8568
8901
  for (const slug of repSlugs) {
8569
- const metaFile = path27.join(setDir, slug, "get_metadata.json");
8570
- if (!existsSync21(metaFile)) continue;
8902
+ const metaFile = path28.join(setDir, slug, "get_metadata.json");
8903
+ if (!existsSync22(metaFile)) continue;
8571
8904
  const name = symbolName(envelopeText(metaFile));
8572
8905
  if (name === void 0) continue;
8573
8906
  const values = /* @__PURE__ */ new Set();
@@ -8666,8 +8999,8 @@ function authorTaskFromSet(setDir, opts = {}) {
8666
8999
  const poses = [];
8667
9000
  const missing = [];
8668
9001
  for (const rep of manifest.reps) {
8669
- const metaFile = path27.join(setDir, rep.slug, "get_metadata.json");
8670
- if (!existsSync21(metaFile)) {
9002
+ const metaFile = path28.join(setDir, rep.slug, "get_metadata.json");
9003
+ if (!existsSync22(metaFile)) {
8671
9004
  missing.push(rep.slug);
8672
9005
  continue;
8673
9006
  }
@@ -8681,8 +9014,8 @@ function authorTaskFromSet(setDir, opts = {}) {
8681
9014
  if (missing.length > 0) {
8682
9015
  throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
8683
9016
  }
8684
- const setMeta = path27.join(setDir, "get_metadata.json");
8685
- const latticeNames = manifest.latticeNames ?? (existsSync21(setMeta) ? [...envelopeText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
9017
+ const setMeta = path28.join(setDir, "get_metadata.json");
9018
+ const latticeNames = manifest.latticeNames ?? (existsSync22(setMeta) ? [...envelopeText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
8686
9019
  const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
8687
9020
  const recordedFonts = recordedFontFamilies(setDir);
8688
9021
  const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
@@ -8767,7 +9100,7 @@ ALL prose instructions live ABOVE the task payload \u2014 the payload contains o
8767
9100
 
8768
9101
  ASSETS: inline the SVG assets you RENDER byte-verbatim, unchanged \u2014 never redraw or approximate an icon. Recorded assets that no scored config displays may be omitted. Two techniques reconcile that rule with reuse (both measured at 1.000): a glyph recorded once but shown in several colors keeps its bytes (fill attribute included) and is repainted with a CSS fill rule \u2014 a CSS declaration outranks an SVG presentation attribute, so one verbatim copy serves every tone; and multi-part glyphs needing fractional placement can nest each verbatim asset as a child <svg x= y=> inside one integer-origin frame (SVG user-space coordinates are exact), an alternative to the transform: scale() pattern.
8769
9102
 
8770
- GEOMETRY ARBITRATION (when emission styling and the recorded box disagree, the BOX wins): Figma strokes are INSIDE the box \u2014 border+padding sums that overshoot a recorded dimension mean use an inset box-shadow or subtract the border from the padding. That rule covers strokes AT the box edge only: when a config's reference PNG is LARGER than its recorded box, the recording itself proves an OUTWARD effect \u2014 a hover ring, glow, or shadow past the frame \u2014 and the outward part is drawn outward (box-shadow spread, outline), never forced inside. On such configs the score report carries a "seat" field ({x, y} \u2014 where the recorded box sits inside the larger reference), derived from the recording's own effect geometry (shadow offset/radius/spread) \u2014 informational, so an offset shadow's asymmetric bleed is not misread as a registration error. Following strokes-inside against a padded reference contradicts the recorded pad and cost a measured five configs a full round. The harness mounts each config at its recorded box (width \xD7 height, floors not clamps); build to those dimensions, not to guessed viewports. Known rasterizer delta: Chrome often seats small text ONE PIXEL HIGHER than Figma in an identically sized box. Correct it with a PAINT-ONLY offset on those text runs \u2014 position: relative with top: 1px \u2014 never with padding or margin, which would grow the recorded box this same paragraph calls truth. TREAT IT AS A MEASUREMENT, NOT A RULE: measured cases are 12px/16px and 14px/20px needing the nudge and 14px/18px not, so font size alone does not predict it and neither does any formula we can currently defend. RULE ORDER when both could apply (run 7: a config sat 0.0004 under the bar AND showed uniform spread \u2014 the rules pointed opposite ways): read the DIFF FIRST; the uniform-spread stop-rule below OUTRANKS the try-it rule here. Only when the diff shows a shifted band: apply the nudge, re-score, and keep it only if it helped. Four measured signatures, so do not expect one: on one kit it drove ink recall to 1.000; on another ink was already 1.000 and only similarity moved (mean 0.961 \u2192 0.976, four configs from 0.003 above the bar to 0.028); on a third it REGRESSED a passing bundle from 9/9 to 3/9 configs and had to be reverted; on a fourth it made every FAILING config worse too (tinted in-section surfaces: ink dropped ~0.011 across all eight configs and pushed a passing one under the bar). It is a hypothesis to score, never a default \u2014 NEVER apply it to a bundle that already passes, and when the failing family's diff shows a uniform low-density spread across the glyph band rather than a shifted band, the cause is rasterization weight, not seating: the nudge cannot help and a heavier hypothesis has no recorded truth to justify it \u2014 report the honest sub-bar result instead. Design-token NAMES in the payload are Figma names \u2014 canonicalize to valid CSS idents (lowercase kebab, e.g. "Text/text-primary" \u2192 --text-text-primary) if you emit tokens.css; literal values are equally acceptable. NEVER invent a token to satisfy a lint finding: quality findings are advisory, and a recorded value the kit has no token for is CORRECT as a literal \u2014 a made-up token name corrupts the tokens file as a record of the kit.
9103
+ GEOMETRY ARBITRATION (when emission styling and the recorded box disagree, the BOX wins): Figma strokes are INSIDE the box \u2014 border+padding sums that overshoot a recorded dimension mean use an inset box-shadow or subtract the border from the padding. That rule covers strokes AT the box edge only: when a config's reference PNG is LARGER than its recorded box, the recording itself proves an OUTWARD effect \u2014 a hover ring, glow, or shadow past the frame \u2014 and the outward part is drawn outward (box-shadow spread, outline), never forced inside. On such configs the score report carries a "seat" field ({x, y} \u2014 where the recorded box sits inside the larger reference), derived from the recording's own effect geometry (shadow offset/radius/spread) \u2014 informational, so an offset shadow's asymmetric bleed is not misread as a registration error. Following strokes-inside against a padded reference contradicts the recorded pad and cost a measured five configs a full round. The harness mounts each config at its recorded box (width \xD7 height) and FLOORS your root to it, never caps it; build to those recorded dimensions, not to guessed viewports \u2014 the prescribed API below says what that floor hides. Known rasterizer delta: Chrome often seats small text ONE PIXEL HIGHER than Figma in an identically sized box. Correct it with a PAINT-ONLY offset on those text runs \u2014 position: relative with top: 1px \u2014 never with padding or margin, which would grow the recorded box this same paragraph calls truth. TREAT IT AS A MEASUREMENT, NOT A RULE: measured cases are 12px/16px and 14px/20px needing the nudge and 14px/18px not, so font size alone does not predict it and neither does any formula we can currently defend. RULE ORDER when both could apply (run 7: a config sat 0.0004 under the bar AND showed uniform spread \u2014 the rules pointed opposite ways): read the DIFF FIRST; the uniform-spread stop-rule below OUTRANKS the try-it rule here. Only when the diff shows a shifted band: apply the nudge, re-score, and keep it only if it helped. Four measured signatures, so do not expect one: on one kit it drove ink recall to 1.000; on another ink was already 1.000 and only similarity moved (mean 0.961 \u2192 0.976, four configs from 0.003 above the bar to 0.028); on a third it REGRESSED a passing bundle from 9/9 to 3/9 configs and had to be reverted; on a fourth it made every FAILING config worse too (tinted in-section surfaces: ink dropped ~0.011 across all eight configs and pushed a passing one under the bar). It is a hypothesis to score, never a default \u2014 NEVER apply it to a bundle that already passes, and when the failing family's diff shows a uniform low-density spread across the glyph band rather than a shifted band, the cause is rasterization weight, not seating: the nudge cannot help and a heavier hypothesis has no recorded truth to justify it \u2014 report the honest sub-bar result instead. Design-token NAMES in the payload are Figma names \u2014 canonicalize to valid CSS idents (lowercase kebab, e.g. "Text/text-primary" \u2192 --text-text-primary) if you emit tokens.css; literal values are equally acceptable. NEVER invent a token to satisfy a lint finding: quality findings are advisory, and a recorded value the kit has no token for is CORRECT as a literal \u2014 a made-up token name corrupts the tokens file as a record of the kit.
8771
9104
 
8772
9105
  RECORDED BOXES ARE TRUTH even when inconsistent: the same string may
8773
9106
  have different recorded widths across variants (designer resizing) \u2014 a
@@ -8784,13 +9117,13 @@ PAINT & PLATFORM TRAPS (each measured on a paid run; every one passed typecheck,
8784
9117
  - A native <dialog> carries user-agent padding: 1em. Override every side, or the root grows past its recorded box and every band inside shifts.
8785
9118
  - THE PAGE CANVAS IS NOT YOURS. Never paint the recording's page background into the component \u2014 no canvas-coloured plates across the root box, no square shadow spread carrying the canvas past the frame. The mount composites your render over the recorded canvas, so transparency wherever the recording shows canvas is both correct and scores correctly; the score report's canvasCoupling counts canvas pixels your render refuses to let the page repaint, and a component that paints the canvas is wrong on every real page.
8786
9119
  - TOKENS SCOPE TO YOUR ROOT CLASS, NEVER :root. Bundles compose on real pages: token names on :root collide across independently generated components and the last stylesheet loaded silently rewrites the others (measured: two colliding names flipped certified surfaces translucent). Declare every token under the component's root class.
8787
- - WHEN TWO AXES BOTH CONTROL PAINT, THEY COMPOSE THROUGH CUSTOM PROPERTIES \u2014 one axis SETS variables, the other CONSUMES them. Direct paint rules on both axes have equal specificity, so source order silently drops one axis for exactly the crossed poses (measured: variant \xD7 tone \u2014 primary+critical rendered dark neutral instead of the recorded red; the sampled scorer never sees crossed poses, so nothing catches it but pixels in use).
9120
+ - WHEN TWO AXES BOTH CONTROL PAINT, THEY COMPOSE THROUGH CUSTOM PROPERTIES \u2014 one axis SETS variables, the other CONSUMES them. Direct paint rules on both axes have equal specificity, so source order silently drops one axis for exactly the crossed poses (measured: variant \xD7 tone \u2014 primary+critical rendered dark neutral instead of the recorded red). CROSSED POSES ARE SCORED: the harness mounts every RECORDED pose, crosses included \u2014 measured on a bundle passing 20/20 (13 single-axis configs, 7 crosses), adding one later same-specificity rule left every single-axis config the rule did not itself target byte-stable and dropped all five crossed configs it touched from ~0.99 to sim 0.09\u20130.16. Only a cross with NO recorded pose goes ungraded.
8788
9121
 
8789
9122
  INTERACTION-READY BY DEFAULT: components are real controls, never static lookalikes \u2014 use the native element matching the archetype (button, input[type=radio|checkbox], select\u2026), real handlers, keyboard operability, and real state (checked/disabled/:hover/:focus-visible). Behavioral checks fail statues. (The forcing-hook rule is specified once, in the prescribed API below.) Your file runs in a bare browser bundle: it must be fully self-contained. IMPORT EVERY React API you use explicitly \u2014 e.g. import { useState, useRef, useEffect } from "react" \u2014 nothing is provided globally; a missing import crashes the mount and every config scores 0.
8790
9123
 
8791
9124
  STATE SEMANTICS (prescribed \u2014 do not choose your own):
8792
9125
  - The component OWNS its interaction state, seeded from the prop. A selection/checked prop supplies the INITIAL value; user interaction updates internal state and must visibly commit without any parent re-render. A purely controlled component that ignores its own clicks fails the behavioral checks. Call an on\u2026Change callback when one is in the prescribed API, but never depend on it to update your own paint.
8793
- - Read-only means operable but not committable: the control stays focusable and in the accessibility tree with aria-readonly="true", and cancels commit on both pointer and keyboard. Do NOT use pointer-events:none or disabled to fake it \u2014 both destroy focusability, and disabled means something different.
9126
+ - Read-only means operable but not committable: the control stays focusable and in the accessibility tree with aria-readonly="true", and cancels commit on both pointer and keyboard. Do NOT fake it with pointer-events:none or disabled. pointer-events:none removes only the POINTER path \u2014 the element stops being hit-tested, so clicks land on whatever is behind it \u2014 while Tab still reaches it and .focus() still lands: the control still takes focus and still commits from the KEYBOARD (measured in Chrome: Space checked a pointer-events:none checkbox). Of the two, ONLY disabled refuses focus, which is why reaching for pointer-events:none to make a pose unfocusable fails disabled-not-focusable with "disabled element took focus".
8794
9127
  - GEOMETRY FOLLOWS PROPS; ONLY PAINT FOLLOWS LIVE STATE. Per-variant sizing must key off the pose props the caller passed, never off live interaction state \u2014 otherwise clicking resizes the component (a recorded selected/unselected width difference is a designer artifact, not a consequence of selection). The scorer renders static poses and structurally cannot see this, so it is on you.
8795
9128
 
8796
9129
  ${systemApi}
@@ -8801,7 +9134,7 @@ RESOLVED FOR THIS RECORDING: it is ${opts.colorScheme}-mode truth, so pin color-
8801
9134
  }
8802
9135
  var PoseCompletenessError, kebab3, camel, pascal, RESERVED_PROPS, axisPropName, isStateAxis, DISMISS_NAMES, symbolName, STYLE_WEIGHTS2, styleWeight;
8803
9136
  var init_brief = __esm({
8804
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/brief.ts"() {
9137
+ "packages/generate/src/brief.ts"() {
8805
9138
  "use strict";
8806
9139
  init_src();
8807
9140
  init_src4();
@@ -8855,11 +9188,11 @@ var init_brief = __esm({
8855
9188
  }
8856
9189
  });
8857
9190
 
8858
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/segments.ts
8859
- import { existsSync as existsSync22, readFileSync as readFileSync19, readdirSync as readdirSync6 } from "node:fs";
8860
- import path28 from "node:path";
9191
+ // packages/generate/src/segments.ts
9192
+ import { existsSync as existsSync23, readFileSync as readFileSync19, readdirSync as readdirSync6 } from "node:fs";
9193
+ import path29 from "node:path";
8861
9194
  function repText(set, rep, tool) {
8862
- const env = JSON.parse(readFileSync19(path28.join(set, rep, `${tool}.json`), "utf8"));
9195
+ const env = JSON.parse(readFileSync19(path29.join(set, rep, `${tool}.json`), "utf8"));
8863
9196
  return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
8864
9197
  }
8865
9198
  function stripFigmaInstructions(emission) {
@@ -8920,16 +9253,16 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
8920
9253
  function buildSegments(task, mode = "fenced") {
8921
9254
  const SET = task.set;
8922
9255
  let rawDefs = {};
8923
- if (existsSync22(path28.join(SET, "get_variable_defs.json"))) {
8924
- const text = envelopeFirstTextPart(JSON.parse(readFileSync19(path28.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
9256
+ if (existsSync23(path29.join(SET, "get_variable_defs.json"))) {
9257
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync19(path29.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
8925
9258
  try {
8926
9259
  rawDefs = JSON.parse(text);
8927
9260
  } catch {
8928
9261
  }
8929
9262
  } else {
8930
9263
  for (const cfg of task.configs) {
8931
- const f = path28.join(SET, cfg.rep, "get_variable_defs.json");
8932
- if (!existsSync22(f)) continue;
9264
+ const f = path29.join(SET, cfg.rep, "get_variable_defs.json");
9265
+ if (!existsSync23(f)) continue;
8933
9266
  const text = envelopeFirstTextPart(JSON.parse(readFileSync19(f, "utf8"))) || "{}";
8934
9267
  try {
8935
9268
  for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
@@ -8938,8 +9271,8 @@ function buildSegments(task, mode = "fenced") {
8938
9271
  }
8939
9272
  }
8940
9273
  const emissionTexts = task.configs.map((cfg) => {
8941
- const f = path28.join(SET, cfg.rep, "get_design_context.json");
8942
- return existsSync22(f) ? envelopeFirstTextPart(JSON.parse(readFileSync19(f, "utf8"))) : "";
9274
+ const f = path29.join(SET, cfg.rep, "get_design_context.json");
9275
+ return existsSync23(f) ? envelopeFirstTextPart(JSON.parse(readFileSync19(f, "utf8"))) : "";
8943
9276
  });
8944
9277
  const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
8945
9278
  const defs = JSON.stringify(map, null, 1);
@@ -8954,9 +9287,9 @@ ${defs}
8954
9287
  for (const cfg of task.configs) {
8955
9288
  const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
8956
9289
  const emission = stripFigmaInstructions(repText(SET, cfg.rep, "get_design_context"));
8957
- const assets = readdirSync6(path28.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
9290
+ const assets = readdirSync6(path29.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
8958
9291
  \`\`\`svg
8959
- ${readFileSync19(path28.join(SET, cfg.rep, f), "utf8")}
9292
+ ${readFileSync19(path29.join(SET, cfg.rep, f), "utf8")}
8960
9293
  \`\`\``).join("\n");
8961
9294
  parts.push(`
8962
9295
  ## Config ${cfg.rep} \u2192 ${cfg.component} ${JSON.stringify(cfg.props)}
@@ -8981,20 +9314,20 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
8981
9314
  } else {
8982
9315
  parts.push(`
8983
9316
  ## Output format
8984
- Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into your candidate directory. Do not paste file contents into chat \u2014 the scorer reads the directory.`);
9317
+ Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path29.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.`);
8985
9318
  }
8986
9319
  return parts.join("\n");
8987
9320
  }
8988
9321
  var cssIdent;
8989
9322
  var init_segments = __esm({
8990
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/segments.ts"() {
9323
+ "packages/generate/src/segments.ts"() {
8991
9324
  "use strict";
8992
9325
  init_src();
8993
9326
  cssIdent = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
8994
9327
  }
8995
9328
  });
8996
9329
 
8997
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/consent.ts
9330
+ // packages/generate/src/consent.ts
8998
9331
  function formatConsentLines(e) {
8999
9332
  return [
9000
9333
  `engine=${e.engineId} model=${e.modelId} route=${e.providerRoute}`,
@@ -9004,12 +9337,12 @@ function formatConsentLines(e) {
9004
9337
  ];
9005
9338
  }
9006
9339
  var init_consent = __esm({
9007
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/consent.ts"() {
9340
+ "packages/generate/src/consent.ts"() {
9008
9341
  "use strict";
9009
9342
  }
9010
9343
  });
9011
9344
 
9012
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/adapter.ts
9345
+ // packages/generate/src/adapter.ts
9013
9346
  function resolveAdapterSettings(entry, overrides = {}) {
9014
9347
  const notes = [];
9015
9348
  let maxTokens = DEFAULT_MAX_TOKENS;
@@ -9041,16 +9374,16 @@ function resolveAdapterSettings(entry, overrides = {}) {
9041
9374
  }
9042
9375
  var DEFAULT_MAX_TOKENS;
9043
9376
  var init_adapter = __esm({
9044
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/adapter.ts"() {
9377
+ "packages/generate/src/adapter.ts"() {
9045
9378
  "use strict";
9046
9379
  DEFAULT_MAX_TOKENS = 2e4;
9047
9380
  }
9048
9381
  });
9049
9382
 
9050
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/bundle-emit.ts
9383
+ // packages/generate/src/bundle-emit.ts
9051
9384
  import { createHash as createHash4 } from "node:crypto";
9052
- import { copyFileSync, existsSync as existsSync23, mkdirSync as mkdirSync7, readFileSync as readFileSync20, readdirSync as readdirSync7, rmSync as rmSync3, writeFileSync as writeFileSync11 } from "node:fs";
9053
- import path29 from "node:path";
9385
+ import { copyFileSync, existsSync as existsSync24, mkdirSync as mkdirSync7, readFileSync as readFileSync20, readdirSync as readdirSync7, rmSync as rmSync3, writeFileSync as writeFileSync11 } from "node:fs";
9386
+ import path30 from "node:path";
9054
9387
  function pinFromConfigs(configs) {
9055
9388
  const domains = /* @__PURE__ */ new Map();
9056
9389
  const kinds = /* @__PURE__ */ new Map();
@@ -9119,9 +9452,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
9119
9452
  const notices = [];
9120
9453
  const licenseTexts = /* @__PURE__ */ new Map();
9121
9454
  for (const face of faces) {
9122
- const src = path29.join(cacheDir, path29.basename(face.file));
9123
- const target = `./fonts/${path29.basename(face.file)}`;
9124
- const format = FONT_FORMATS[path29.extname(face.file).toLowerCase()] ?? "truetype";
9455
+ const src = path30.join(cacheDir, path30.basename(face.file));
9456
+ const target = `./fonts/${path30.basename(face.file)}`;
9457
+ const format = FONT_FORMATS[path30.extname(face.file).toLowerCase()] ?? "truetype";
9125
9458
  const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
9126
9459
  const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
9127
9460
  const license = normalizeFontLicense(face.license);
@@ -9151,14 +9484,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
9151
9484
  `/* ${decl} */`
9152
9485
  );
9153
9486
  }
9154
- } else if (existsSync23(src) && createHash4("sha256").update(readFileSync20(src)).digest("hex") === face.sha256) {
9155
- mkdirSync7(path29.join(bundleDir, "fonts"), { recursive: true });
9156
- copyFileSync(src, path29.join(bundleDir, "fonts", path29.basename(face.file)));
9487
+ } else if (existsSync24(src) && createHash4("sha256").update(readFileSync20(src)).digest("hex") === face.sha256) {
9488
+ mkdirSync7(path30.join(bundleDir, "fonts"), { recursive: true });
9489
+ copyFileSync(src, path30.join(bundleDir, "fonts", path30.basename(face.file)));
9157
9490
  licenseTexts.set(terms.file, terms.text);
9158
9491
  const upstream = upstreamAttribution(face);
9159
9492
  notices.push(
9160
9493
  "",
9161
- `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path29.basename(face.file)}`,
9494
+ `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path30.basename(face.file)}`,
9162
9495
  ` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
9163
9496
  ` source: ${face.source}`,
9164
9497
  ` sha256: ${face.sha256}`,
@@ -9172,9 +9505,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
9172
9505
  }
9173
9506
  if (lines.length === 0) return null;
9174
9507
  if (notices.length > 0) {
9175
- const fontsDir = path29.join(bundleDir, "fonts");
9176
- for (const [file, text] of licenseTexts) writeFileSync11(path29.join(fontsDir, file), text);
9177
- writeFileSync11(path29.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
9508
+ const fontsDir = path30.join(bundleDir, "fonts");
9509
+ for (const [file, text] of licenseTexts) writeFileSync11(path30.join(fontsDir, file), text);
9510
+ writeFileSync11(path30.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
9178
9511
  `);
9179
9512
  header.push(
9180
9513
  "/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
@@ -9186,8 +9519,8 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
9186
9519
  `;
9187
9520
  }
9188
9521
  function countLatticeSymbols(setDir) {
9189
- const manifestFile = path29.join(setDir, "recording-set.json");
9190
- if (existsSync23(manifestFile)) {
9522
+ const manifestFile = path30.join(setDir, "recording-set.json");
9523
+ if (existsSync24(manifestFile)) {
9191
9524
  try {
9192
9525
  const lattice = JSON.parse(readFileSync20(manifestFile, "utf8")).latticeNames;
9193
9526
  if (lattice !== void 0 && lattice.length > 0) return lattice.length;
@@ -9195,9 +9528,9 @@ function countLatticeSymbols(setDir) {
9195
9528
  }
9196
9529
  }
9197
9530
  const files = [
9198
- path29.join(setDir, "get_metadata.json"),
9199
- ...existsSync23(setDir) ? readdirSync7(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path29.join(setDir, f)) : []
9200
- ].filter((f) => existsSync23(f));
9531
+ path30.join(setDir, "get_metadata.json"),
9532
+ ...existsSync24(setDir) ? readdirSync7(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path30.join(setDir, f)) : []
9533
+ ].filter((f) => existsSync24(f));
9201
9534
  if (files.length === 0) return null;
9202
9535
  let count = 0;
9203
9536
  for (const f of files) {
@@ -9209,21 +9542,21 @@ function countLatticeSymbols(setDir) {
9209
9542
  function recordingSetHash(setDir, configs) {
9210
9543
  const relPaths = [];
9211
9544
  for (const name of ["recording-set.json", "completeness-manifest.json", "get_variable_defs.json", "get_metadata.json"]) {
9212
- if (existsSync23(path29.join(setDir, name))) relPaths.push(name);
9545
+ if (existsSync24(path30.join(setDir, name))) relPaths.push(name);
9213
9546
  }
9214
9547
  for (const cfg of configs) {
9215
9548
  for (const f of ["get_design_context.json", "get_metadata.json", "get_screenshot.json", "get_variable_defs.json"]) {
9216
- if (existsSync23(path29.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
9549
+ if (existsSync24(path30.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
9217
9550
  }
9218
- if (existsSync23(path29.join(setDir, cfg.rep))) {
9219
- for (const asset of readdirSync7(path29.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
9551
+ if (existsSync24(path30.join(setDir, cfg.rep))) {
9552
+ for (const asset of readdirSync7(path30.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
9220
9553
  relPaths.push(`${cfg.rep}/${asset}`);
9221
9554
  }
9222
9555
  }
9223
9556
  }
9224
9557
  return hashRecordingSet(
9225
9558
  relPaths,
9226
- (p) => new Uint8Array(readFileSync20(path29.join(setDir, p))),
9559
+ (p) => new Uint8Array(readFileSync20(path30.join(setDir, p))),
9227
9560
  (chunks) => {
9228
9561
  const h = createHash4("sha256");
9229
9562
  for (const c of chunks) h.update(c);
@@ -9246,7 +9579,7 @@ function emitBundleV1(opts) {
9246
9579
  const lattice = countLatticeSymbols(opts.task.set);
9247
9580
  const interaction = opts.behaviors.filter((b) => !b.id.startsWith("prelude:"));
9248
9581
  const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
9249
- const cssFiles = ["styles.css", "tokens.css"].map((f) => path29.join(opts.bundleDir, f)).filter((f) => existsSync23(f));
9582
+ const cssFiles = ["styles.css", "tokens.css"].map((f) => path30.join(opts.bundleDir, f)).filter((f) => existsSync24(f));
9250
9583
  const families = cssFontFamilies(cssFiles.map((f) => readFileSync20(f, "utf8")).join("\n"));
9251
9584
  const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
9252
9585
  family: f.family,
@@ -9276,7 +9609,7 @@ function emitBundleV1(opts) {
9276
9609
  // resolvable via verify's --set override).
9277
9610
  path: (() => {
9278
9611
  const base = process.env["INIT_CWD"] ?? process.cwd();
9279
- const rel = path29.relative(base, opts.task.set);
9612
+ const rel = path30.relative(base, opts.task.set);
9280
9613
  return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
9281
9614
  })(),
9282
9615
  component: opts.componentName,
@@ -9300,12 +9633,12 @@ function emitBundleV1(opts) {
9300
9633
  })
9301
9634
  };
9302
9635
  const written = [];
9303
- const manifestPath2 = path29.join(opts.bundleDir, "component.json");
9636
+ const manifestPath2 = path30.join(opts.bundleDir, "component.json");
9304
9637
  writeFileSync11(manifestPath2, `${JSON.stringify(manifest, null, 2)}
9305
9638
  `);
9306
9639
  written.push(manifestPath2);
9307
- const stylesPath = path29.join(opts.bundleDir, "styles.css");
9308
- if (existsSync23(stylesPath)) {
9640
+ const stylesPath = path30.join(opts.bundleDir, "styles.css");
9641
+ if (existsSync24(stylesPath)) {
9309
9642
  const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
9310
9643
  const current = readFileSync20(stylesPath, "utf8");
9311
9644
  const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
@@ -9313,8 +9646,8 @@ function emitBundleV1(opts) {
9313
9646
  ${stripped}`);
9314
9647
  written.push(stylesPath);
9315
9648
  }
9316
- const fontsCssPath = path29.join(opts.bundleDir, "fonts.css");
9317
- rmSync3(path29.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
9649
+ const fontsCssPath = path30.join(opts.bundleDir, "fonts.css");
9650
+ rmSync3(path30.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
9318
9651
  rmSync3(fontsCssPath, { force: true });
9319
9652
  const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
9320
9653
  if (fontsCss !== null) {
@@ -9327,7 +9660,7 @@ ${stripped}`);
9327
9660
  }
9328
9661
  var FONT_FORMATS, BARS, NOTICE_PREAMBLE, OFL_1_1_TEXT, UFL_1_0_TEXT, APACHE_2_0_TEXT, REDISTRIBUTABLE;
9329
9662
  var init_bundle_emit = __esm({
9330
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/bundle-emit.ts"() {
9663
+ "packages/generate/src/bundle-emit.ts"() {
9331
9664
  "use strict";
9332
9665
  init_src6();
9333
9666
  init_src();
@@ -9742,9 +10075,9 @@ DEALINGS IN THE FONT SOFTWARE.
9742
10075
  }
9743
10076
  });
9744
10077
 
9745
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/index.ts
10078
+ // packages/generate/src/index.ts
9746
10079
  var init_src7 = __esm({
9747
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/generate/src/index.ts"() {
10080
+ "packages/generate/src/index.ts"() {
9748
10081
  "use strict";
9749
10082
  init_engine();
9750
10083
  init_engine_curated();
@@ -9758,7 +10091,7 @@ var init_src7 = __esm({
9758
10091
  }
9759
10092
  });
9760
10093
 
9761
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/fonts.ts
10094
+ // packages/cli/src/commands/fonts.ts
9762
10095
  var fonts_exports = {};
9763
10096
  __export(fonts_exports, {
9764
10097
  DEFAULT_FONT_CACHE: () => DEFAULT_FONT_CACHE,
@@ -9768,8 +10101,8 @@ __export(fonts_exports, {
9768
10101
  runFontsResolveSet: () => runFontsResolveSet,
9769
10102
  runFontsStatus: () => runFontsStatus
9770
10103
  });
9771
- import { existsSync as existsSync24, readFileSync as readFileSync21 } from "node:fs";
9772
- import path30 from "node:path";
10104
+ import { existsSync as existsSync25, readFileSync as readFileSync21 } from "node:fs";
10105
+ import path31 from "node:path";
9773
10106
  async function runFontsResolve(opts) {
9774
10107
  const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
9775
10108
  emitData(opts, result, () => {
@@ -9784,7 +10117,7 @@ async function runFontsResolve(opts) {
9784
10117
  }
9785
10118
  }
9786
10119
  async function runFontsResolveSet(opts) {
9787
- const setDir = path30.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
10120
+ const setDir = path31.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
9788
10121
  let needs = [];
9789
10122
  try {
9790
10123
  needs = recordedFontNeeds(setDir);
@@ -9792,19 +10125,19 @@ async function runFontsResolveSet(opts) {
9792
10125
  fail(opts, ExitCode.InputValidation, {
9793
10126
  error: `cannot read font needs from ${setDir}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
9794
10127
  code: "font-needs-unreadable",
9795
- remediation: 'Point --set at a directory recorded with `tendril record` (it carries recording-set.json), or resolve a family explicitly: tendril fonts resolve "<Family>" --weights 400 500 600.'
10128
+ remediation: `Point --set at a directory recorded with \`${tendrilCommand("record plan")}\` (it carries recording-set.json), or resolve a family explicitly: ${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}.`
9796
10129
  });
9797
10130
  }
9798
10131
  if (needs.length === 0) {
9799
10132
  fail(opts, ExitCode.InputValidation, {
9800
10133
  error: `recording set ${setDir} declares no detectable font families`,
9801
10134
  code: "font-needs-empty",
9802
- remediation: 'Resolve the family the design uses explicitly: tendril fonts resolve "<Family>" --weights 400 500 600.'
10135
+ remediation: `Resolve the family the design uses explicitly: ${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}.`
9803
10136
  });
9804
10137
  }
9805
10138
  const inCache = new Set(resolvedFontFamilies(opts.cacheDir).flatMap((f) => f.weights.map((w) => `${f.family.toLowerCase()}:${w}`)));
9806
10139
  const unstamped = new Map(facesMissingLicense(opts.cacheDir).map((f) => [`${f.family.toLowerCase()}:${f.weight}`, f.sha256]));
9807
- const cached = [];
10140
+ const cached2 = [];
9808
10141
  const resolved = [];
9809
10142
  const failures = [];
9810
10143
  const relicensed = [];
@@ -9817,7 +10150,7 @@ async function runFontsResolveSet(opts) {
9817
10150
  const key = `${need.family.toLowerCase()}:${w}`;
9818
10151
  if (!inCache.has(key)) missing.push(w);
9819
10152
  else if (unstamped.has(key)) toStamp.push(w);
9820
- else cached.push({ family: need.family, weight: w });
10153
+ else cached2.push({ family: need.family, weight: w });
9821
10154
  }
9822
10155
  if (missing.length > 0) {
9823
10156
  const result = await resolveFonts(need.family, missing, opts.cacheDir);
@@ -9834,10 +10167,10 @@ async function runFontsResolveSet(opts) {
9834
10167
  unlicensed.push(...result.failures);
9835
10168
  }
9836
10169
  }
9837
- emitData(opts, { set: setDir, needs, cached, resolved, relicensed, unlicensed, failures }, () => {
10170
+ emitData(opts, { set: setDir, needs, cached: cached2, resolved, relicensed, unlicensed, failures }, () => {
9838
10171
  process.stdout.write(`set declares: ${needs.map((n) => `${n.family} (${n.weights.join(", ")})`).join(" \xB7 ")}
9839
10172
  `);
9840
- for (const f of cached) process.stdout.write(`cached ${f.family} ${f.weight}
10173
+ for (const f of cached2) process.stdout.write(`cached ${f.family} ${f.weight}
9841
10174
  `);
9842
10175
  for (const f of resolved) process.stdout.write(`resolved ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
9843
10176
  `);
@@ -9857,16 +10190,16 @@ async function runFontsResolveSet(opts) {
9857
10190
  }
9858
10191
  }
9859
10192
  function runFontsStatus(opts) {
9860
- const manifestPath2 = path30.join(opts.cacheDir, "manifest.json");
9861
- if (!existsSync24(manifestPath2)) {
10193
+ const manifestPath2 = path31.join(opts.cacheDir, "manifest.json");
10194
+ if (!existsSync25(manifestPath2)) {
9862
10195
  fail(opts, ExitCode.FontsUnproven, {
9863
10196
  error: `no font cache at ${opts.cacheDir}`,
9864
10197
  code: "fonts-unresolved",
9865
- remediation: 'Run `tendril fonts resolve --set <recording-dir>` (or `tendril fonts resolve "<Family>" --weights 400 500 600`) first.'
10198
+ remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
9866
10199
  });
9867
10200
  }
9868
10201
  const faces = JSON.parse(readFileSync21(manifestPath2, "utf8"));
9869
- const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path30.resolve(opts.lock), opts.cacheDir) : null;
10202
+ const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path31.resolve(opts.lock), opts.cacheDir) : null;
9870
10203
  emitData(opts, { faces, lockVerdicts }, () => {
9871
10204
  for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
9872
10205
  `);
@@ -9897,7 +10230,10 @@ function runFontsAdd(opts) {
9897
10230
  fail(opts, ExitCode.InputValidation, {
9898
10231
  error: err instanceof Error ? err.message : String(err),
9899
10232
  code: "font-add-failed",
9900
- remediation: 'Pass a font file you are licensed to use: tendril fonts add "S\xF6hne" 400 ./fonts/soehne-400.woff2'
10233
+ // The face file is the user's own licensed asset: this command
10234
+ // can name every argument but that one, so it stays a marked
10235
+ // blank rather than a fabricated path that looks real.
10236
+ remediation: `Pass a font file you are licensed to use, by absolute path: ${tendrilCommand(`fonts add "${opts.family}" ${opts.weight} <absolute-path-to-face.woff2>`)}`
9901
10237
  });
9902
10238
  }
9903
10239
  emitData(opts, face, () => {
@@ -9906,28 +10242,32 @@ function runFontsAdd(opts) {
9906
10242
  });
9907
10243
  }
9908
10244
  var init_fonts = __esm({
9909
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/fonts.ts"() {
10245
+ "packages/cli/src/commands/fonts.ts"() {
9910
10246
  "use strict";
9911
10247
  init_src3();
9912
10248
  init_src4();
9913
10249
  init_src7();
9914
10250
  init_output();
10251
+ init_invocation();
9915
10252
  }
9916
10253
  });
9917
10254
 
9918
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/font-guidance.ts
10255
+ // packages/cli/src/font-guidance.ts
10256
+ import path32 from "node:path";
9919
10257
  function fontsUnprovenRemediation(setDir) {
9920
- if (setDir !== void 0) {
10258
+ const set = setDir === void 0 ? void 0 : path32.resolve(setDir);
10259
+ if (set !== void 0) {
9921
10260
  try {
9922
- const needs = recordedFontNeeds(setDir);
10261
+ const needs = recordedFontNeeds(set);
9923
10262
  if (needs.length > 0) {
9924
10263
  const families = needs.map((n) => `"${n.family}"`).join(", ");
9925
- return `Run \`tendril fonts resolve --set ${setDir}\` \u2014 the recording declares ${families}; faces land in ${DEFAULT_FONT_CACHE}.`;
10264
+ return `Run \`${tendrilCommand(`fonts resolve --set ${set}`)}\` \u2014 the recording declares ${families}; faces land in ${DEFAULT_FONT_CACHE}.`;
9926
10265
  }
9927
10266
  } catch {
9928
10267
  }
9929
10268
  }
9930
- return `Run \`tendril fonts resolve --set <recording-dir>\` (or \`tendril fonts resolve "<Family>" --weights 400 500 600\`) to populate the font cache \u2014 the cache read is ${DEFAULT_FONT_CACHE}.`;
10269
+ const target = set ?? "<recording-dir>";
10270
+ return `Run \`${tendrilCommand(`fonts resolve --set ${target}`)}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) to populate the font cache \u2014 the cache read is ${DEFAULT_FONT_CACHE}.`;
9931
10271
  }
9932
10272
  function taskFontFamilies(setDir) {
9933
10273
  try {
@@ -9969,28 +10309,33 @@ function missingWeights(setDir, cacheDir) {
9969
10309
  return gaps;
9970
10310
  }
9971
10311
  var init_font_guidance = __esm({
9972
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/font-guidance.ts"() {
10312
+ "packages/cli/src/font-guidance.ts"() {
9973
10313
  "use strict";
9974
10314
  init_src4();
9975
10315
  init_src7();
10316
+ init_invocation();
9976
10317
  }
9977
10318
  });
9978
10319
 
9979
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/verify.ts
10320
+ // packages/cli/src/commands/verify.ts
9980
10321
  var verify_exports = {};
9981
10322
  __export(verify_exports, {
9982
10323
  NO_ROLE_MANIFEST: () => NO_ROLE_MANIFEST,
9983
10324
  ROLES_NOT_RESOLVED: () => ROLES_NOT_RESOLVED,
9984
10325
  checkSummarySegments: () => checkSummarySegments,
9985
10326
  compositionReport: () => compositionReport,
10327
+ eyeCheck: () => eyeCheck,
9986
10328
  failureTally: () => failureTally,
9987
10329
  foldConfigStatus: () => foldConfigStatus,
9988
10330
  interactionCoverage: () => interactionCoverage,
10331
+ occlusionReport: () => occlusionReport,
10332
+ operabilityLine: () => operabilityLine,
10333
+ operabilityReport: () => operabilityReport,
9989
10334
  resolveComposition: () => resolveComposition,
9990
10335
  runVerify: () => runVerify
9991
10336
  });
9992
- import { existsSync as existsSync25, readFileSync as readFileSync22 } from "node:fs";
9993
- import path31 from "node:path";
10337
+ import { existsSync as existsSync26, readFileSync as readFileSync22 } from "node:fs";
10338
+ import path33 from "node:path";
9994
10339
  function interactionCoverage(behaviors) {
9995
10340
  const interaction = behaviors.filter((b) => !b.id.startsWith("prelude:"));
9996
10341
  return {
@@ -10000,6 +10345,43 @@ function interactionCoverage(behaviors) {
10000
10345
  operability: interaction.length > 0 && interaction.every((b) => b.pass) ? "verified" : "unverified"
10001
10346
  };
10002
10347
  }
10348
+ function operabilityReport(input) {
10349
+ const { interactionChecks, interactionPassed, preludeChecks } = interactionCoverage(input.behaviors);
10350
+ const prelude = ` The ${preludeChecks} behavior check(s) that did run are page-level prelude style hygiene, which says nothing about whether the component works.`;
10351
+ if (interactionChecks === 0) {
10352
+ const evidence = input.interactionEvidence;
10353
+ if (evidence === void 0) {
10354
+ return {
10355
+ short: "nothing authored; recorded poses never derived",
10356
+ unavailable: `no interaction behaviours were authored for this component, and this run never derived the recording's interactive poses \u2014 nothing about focus, typing, or keyboard operation was measured, and nothing here rules out a static lookalike.${prelude}`
10357
+ };
10358
+ }
10359
+ if (evidence.length > 0) {
10360
+ return {
10361
+ short: "interactive poses recorded, none authored",
10362
+ unavailable: `the recording PROVES interactive poses (${evidence.join(
10363
+ ", "
10364
+ )}) and ZERO interaction behaviours were authored to cover them \u2014 nothing about focus, typing, or keyboard operation was measured; this is an instrument failure, not a working component.${prelude}`
10365
+ };
10366
+ }
10367
+ return { short: "no interactive poses recorded", unavailable: `${NO_INTERACTIVE_POSES}${prelude}` };
10368
+ }
10369
+ if (interactionPassed < interactionChecks) {
10370
+ const failed = interactionChecks - interactionPassed;
10371
+ return {
10372
+ checks: interactionChecks,
10373
+ passed: interactionPassed,
10374
+ short: `${failed} of ${interactionChecks} interaction check(s) failed`,
10375
+ unverified: `the interaction checks RAN and ${failed} of ${interactionChecks} failed (each named in the FAIL behavior rows) \u2014 this component WAS exercised and did not behave as the recording requires; unverified here means measured and wrong, not unmeasured.`
10376
+ };
10377
+ }
10378
+ return { checks: interactionChecks, passed: interactionPassed };
10379
+ }
10380
+ function operabilityLine(state) {
10381
+ if ("unavailable" in state) return `UNVERIFIED operability \u2014 ${state.unavailable}`;
10382
+ if ("unverified" in state) return `UNVERIFIED operability \u2014 ${state.unverified}`;
10383
+ return void 0;
10384
+ }
10003
10385
  function foldConfigStatus(s, failDemotions, substitutedFamilies) {
10004
10386
  const { exact: _exact, ...reported } = s;
10005
10387
  let status = tierOf(s, BARS2.cert);
@@ -10038,8 +10420,18 @@ function checkSummarySegments(input) {
10038
10420
  const tally = (rows) => `${rows.filter((r) => r.pass).length}/${rows.length}`;
10039
10421
  const { availability } = input;
10040
10422
  const composition = availability.unavailable !== void 0 ? `composition NOT CHECKED (${availability.short})` : `composition ${tally(input.structural)} structural, ${input.crops !== void 0 ? `${tally(input.crops)} crops` : "crops unavailable"} (roles: ${rolesSourceLabel(availability.roles)}${(availability.roles.narrowingAccepted ?? []).length > 0 ? `, ${(availability.roles.narrowingAccepted ?? []).length} narrowing(s) accepted: ${(availability.roles.narrowingAccepted ?? []).join(", ")}` : ""})`;
10041
- const occlusion = input.occlusion.length === 0 ? "occlusion not applicable (no overlay declared)" : `occlusion ${tally(input.occlusion)}`;
10042
- return ` \xB7 ${composition} \xB7 ${occlusion}`;
10423
+ const occ = occlusionReport(input.occlusion);
10424
+ const occlusion = "unavailable" in occ ? `occlusion not applicable (${NO_OVERLAY_DECLARED})` : `occlusion ${tally(input.occlusion)}`;
10425
+ const operability = "short" in input.operability ? `operability UNVERIFIED (${input.operability.short})` : `operability ${input.operability.passed}/${input.operability.checks} interaction`;
10426
+ return ` \xB7 ${operability} \xB7 ${composition} \xB7 ${occlusion}`;
10427
+ }
10428
+ function occlusionReport(occlusion) {
10429
+ if (occlusion.length === 0) {
10430
+ return {
10431
+ unavailable: "the bundle declares no overlay (no open-state config and no prelude popover) \u2014 the sibling-occlusion check DOES NOT APPLY and nothing about overlay stacking was measured; an empty check list is this state, not a clean bill"
10432
+ };
10433
+ }
10434
+ return { checks: occlusion.length, passed: occlusion.filter((o) => o.pass).length };
10043
10435
  }
10044
10436
  function compositionReport(input) {
10045
10437
  const { availability, regions } = input;
@@ -10057,6 +10449,13 @@ function compositionReport(input) {
10057
10449
  crops: input.crops ?? { unavailable: cropsUnavailable }
10058
10450
  };
10059
10451
  }
10452
+ function eyeCheck(bundleDir) {
10453
+ return {
10454
+ command: tendrilCommand(`inspect "${bundleDir}"`),
10455
+ sheetPath: path33.join(bundleDir, "verify-evidence", "inspect.html"),
10456
+ 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."
10457
+ };
10458
+ }
10060
10459
  function failureTally(t) {
10061
10460
  return `${t.configs.length} config(s), ${t.behaviors.length} behavior(s), ${t.structural.length + t.crops.length} composition check(s), ${t.occlusion.length} occlusion check(s) below the ${t.bar} bar${t.evidenceUnverified ? "; PLUS the interaction-evidence instrument gate failed (operability could not be verified \u2014 see FAIL interaction-evidence above)" : ""} \u2014 bundle written, verdict honest (Q4)`;
10062
10461
  }
@@ -10065,7 +10464,7 @@ function taskFromManifest(opts, manifest, setDir) {
10065
10464
  const adapterSlugs = Object.keys(manifest.propAdapter);
10066
10465
  const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
10067
10466
  const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
10068
- const registry = Object.values(TASKS).find((t) => path31.resolve(t.set) === path31.resolve(setDir));
10467
+ const registry = Object.values(TASKS).find((t) => path33.resolve(t.set) === path33.resolve(setDir));
10069
10468
  const authored = (() => {
10070
10469
  if (registry !== void 0) return void 0;
10071
10470
  try {
@@ -10074,7 +10473,7 @@ function taskFromManifest(opts, manifest, setDir) {
10074
10473
  fail(opts, ExitCode.InputValidation, {
10075
10474
  error: `cannot author the verification task from ${setDir}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
10076
10475
  code: "AUTHORING_FAILED",
10077
- remediation: "Fix the recording set (duplicate/colliding poses fail authoring by design \u2014 check `tendril record status` and the set's variant names)."
10476
+ remediation: `Fix the recording set (duplicate/colliding poses fail authoring by design \u2014 check \`${tendrilCommand(`record status --set ${setDir}`)}\` and the set's variant names).`
10078
10477
  });
10079
10478
  }
10080
10479
  })();
@@ -10091,24 +10490,27 @@ function taskFromManifest(opts, manifest, setDir) {
10091
10490
  task,
10092
10491
  unmapped,
10093
10492
  adapterOnly,
10094
- interactionEvidence: authored?.api.interactionEvidence ?? [],
10493
+ // Registry sets carry hand-declared behaviors and no derivation
10494
+ // runs, so their evidence is UNKNOWN, not empty: an empty list is
10495
+ // spent downstream as "the recording holds no interactive pose".
10496
+ interactionEvidence: authored?.api.interactionEvidence,
10095
10497
  unmappedInteractionEvidence: authored?.api.unmappedInteractionEvidence ?? []
10096
10498
  };
10097
10499
  }
10098
10500
  async function runVerify(opts) {
10099
10501
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
10100
- const setOverride = opts.set !== void 0 ? path31.resolve(callerCwd, opts.set) : void 0;
10101
- opts = { ...opts, bundleDir: path31.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
10102
- if (!existsSync25(opts.bundleDir)) {
10502
+ const setOverride = opts.set !== void 0 ? path33.resolve(callerCwd, opts.set) : void 0;
10503
+ opts = { ...opts, bundleDir: path33.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
10504
+ if (!existsSync26(opts.bundleDir)) {
10103
10505
  fail(opts, ExitCode.InputValidation, {
10104
10506
  error: `bundle directory not found: ${opts.bundleDir}`,
10105
10507
  code: "bundle-missing",
10106
10508
  remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
10107
10509
  });
10108
10510
  }
10109
- const manifestPath2 = path31.join(opts.bundleDir, "component.json");
10511
+ const manifestPath2 = path33.join(opts.bundleDir, "component.json");
10110
10512
  let manifest;
10111
- if (existsSync25(manifestPath2)) {
10513
+ if (existsSync26(manifestPath2)) {
10112
10514
  const { manifest: parsed, issues } = readBundleManifest(readFileSync22(manifestPath2, "utf8"));
10113
10515
  if (issues.length > 0) {
10114
10516
  fail(opts, ExitCode.InputValidation, {
@@ -10121,7 +10523,7 @@ async function runVerify(opts) {
10121
10523
  }
10122
10524
  let task;
10123
10525
  let unmapped = [];
10124
- let interactionEvidence = [];
10526
+ let interactionEvidence;
10125
10527
  let unmappedInteractionEvidence = [];
10126
10528
  let availability = ROLES_NOT_RESOLVED;
10127
10529
  if (opts.task !== void 0) {
@@ -10136,21 +10538,21 @@ async function runVerify(opts) {
10136
10538
  task = registry;
10137
10539
  } else if (manifest !== void 0) {
10138
10540
  const resolveSetDir = (p) => {
10139
- if (path31.isAbsolute(p)) return p;
10140
- const fromRepo = path31.resolve(REPO_ROOT, p);
10141
- if (existsSync25(fromRepo)) return fromRepo;
10142
- return path31.resolve(callerCwd, p);
10541
+ if (path33.isAbsolute(p)) return p;
10542
+ const fromRepo = path33.resolve(REPO_ROOT, p);
10543
+ if (existsSync26(fromRepo)) return fromRepo;
10544
+ return path33.resolve(callerCwd, p);
10143
10545
  };
10144
10546
  const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
10145
- if (!existsSync25(path31.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path31.resolve(t.set) === path31.resolve(setDir))) {
10547
+ if (!existsSync26(path33.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path33.resolve(t.set) === path33.resolve(setDir))) {
10146
10548
  fail(opts, ExitCode.RecordingIncomplete, {
10147
10549
  error: `recording set not found or unmanifested: ${setDir}`,
10148
10550
  code: "recording-set-missing",
10149
10551
  remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
10150
10552
  });
10151
10553
  }
10152
- const registry = Object.values(TASKS).find((t) => path31.resolve(t.set) === path31.resolve(setDir));
10153
- if (registry !== void 0 && !existsSync25(path31.join(setDir, "recording-set.json"))) {
10554
+ const registry = Object.values(TASKS).find((t) => path33.resolve(t.set) === path33.resolve(setDir));
10555
+ if (registry !== void 0 && !existsSync26(path33.join(setDir, "recording-set.json"))) {
10154
10556
  const recordedSlugs = registry.configs.map((c) => c.rep);
10155
10557
  const adapterSlugs = Object.keys(manifest.propAdapter);
10156
10558
  unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
@@ -10174,8 +10576,8 @@ async function runVerify(opts) {
10174
10576
  warn(opts, `recording set content differs from the bundle's provenance stamp (${hash.slice(0, 12)}\u2026 vs ${manifest.provenance.recordingSet.hash.slice(0, 12)}\u2026) \u2014 scores apply to the CURRENT set`);
10175
10577
  }
10176
10578
  for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
10177
- const p = path31.join(opts.bundleDir, name);
10178
- if (!existsSync25(p)) continue;
10579
+ const p = path33.join(opts.bundleDir, name);
10580
+ if (!existsSync26(p)) continue;
10179
10581
  const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync22(p)));
10180
10582
  if (issues.length > 0) {
10181
10583
  fail(opts, ExitCode.InputValidation, {
@@ -10214,7 +10616,7 @@ async function runVerify(opts) {
10214
10616
  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)`);
10215
10617
  }
10216
10618
  const missing = task.configs.filter(
10217
- (c) => !existsSync25(path31.join(task.set, c.rep, "get_screenshot.json")) || !existsSync25(path31.join(task.set, c.rep, "get_metadata.json"))
10619
+ (c) => !existsSync26(path33.join(task.set, c.rep, "get_screenshot.json")) || !existsSync26(path33.join(task.set, c.rep, "get_metadata.json"))
10218
10620
  );
10219
10621
  if (missing.length > 0) {
10220
10622
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -10224,10 +10626,10 @@ async function runVerify(opts) {
10224
10626
  });
10225
10627
  }
10226
10628
  const bar = BARS2[opts.bar];
10227
- const evidenceDir = path31.join(opts.bundleDir, "verify-evidence");
10629
+ const evidenceDir = path33.join(opts.bundleDir, "verify-evidence");
10228
10630
  const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
10229
10631
  const quality = await checkBundleQuality(opts.bundleDir, task.entry, { dir: task.set, reps: task.configs.map((c) => c.rep) });
10230
- const bundleCss = ["tokens.css", "styles.css"].map((f) => path31.join(opts.bundleDir, f)).filter((f) => existsSync25(f)).map((f) => readFileSync22(f, "utf8")).join("\n");
10632
+ const bundleCss = ["tokens.css", "styles.css"].map((f) => path33.join(opts.bundleDir, f)).filter((f) => existsSync26(f)).map((f) => readFileSync22(f, "utf8")).join("\n");
10231
10633
  const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss);
10232
10634
  const parity = await checkHoverParity(task, opts.bundleDir);
10233
10635
  const behaviors = [...await checkBehaviors(task, opts.bundleDir), ...parity];
@@ -10258,7 +10660,8 @@ async function runVerify(opts) {
10258
10660
  const certified = statuses.filter((s) => s.status === "certified").length;
10259
10661
  const occlusionFailures = occlusion.filter((o) => !o.pass);
10260
10662
  const coverage = interactionCoverage(behaviors);
10261
- const evidenceUnverified = interactionEvidence.length > 0 && coverage.interactionChecks === 0;
10663
+ const operability = operabilityReport({ behaviors, interactionEvidence });
10664
+ const evidenceUnverified = (interactionEvidence?.length ?? 0) > 0 && coverage.interactionChecks === 0;
10262
10665
  const okExceptDemotion = pixelFailures.length === 0 && behaviorFailures.length === 0 && structuralFailures.length === 0 && cropFailures.length === 0 && occlusionFailures.length === 0 && !evidenceUnverified;
10263
10666
  const certBlockedByAbsentInk = opts.bar === "cert" ? absentInkDemoted : [];
10264
10667
  const ok = okExceptDemotion && certBlockedByAbsentInk.length === 0;
@@ -10298,7 +10701,11 @@ async function runVerify(opts) {
10298
10701
  // read exactly like a verified one — measured, a statue and a real
10299
10702
  // control produced the same 6/6. The split is the honest form.
10300
10703
  ...coverage,
10301
- ...interactionEvidence.length > 0 ? { interactionEvidence } : {},
10704
+ ...interactionEvidence !== void 0 && interactionEvidence.length > 0 ? { interactionEvidence } : {},
10705
+ // The one-word verdict's CAUSE, in the channel that has no summary
10706
+ // line to carry it. `operability` above keeps its key and values;
10707
+ // this says which of its meanings the run actually produced.
10708
+ operabilityCheck: operability,
10302
10709
  // Poses the recording proves interactive that no operability
10303
10710
  // check covers — pixel-verified only. The first cold kit shipped
10304
10711
  // pressed/focus this way with nothing in the report saying so.
@@ -10312,6 +10719,10 @@ async function runVerify(opts) {
10312
10719
  // silently inherited neither.
10313
10720
  quality: { findings: quality.findings, tokensAbsent: quality.tokensAbsent },
10314
10721
  occlusion,
10722
+ // The rows' STATE, which the rows alone cannot carry (see
10723
+ // occlusionReport): the agent channel had no counterpart to the
10724
+ // human line's "occlusion not applicable (no overlay declared)".
10725
+ occlusionCheck: occlusionReport(occlusion),
10315
10726
  configs: statuses,
10316
10727
  behaviors,
10317
10728
  evidence: { dir: evidenceDir, files: "per config: <rep>-render.png, <rep>-ref.png, <rep>-diff.png" },
@@ -10323,15 +10734,7 @@ async function runVerify(opts) {
10323
10734
  ...certBlockedByAbsentInk.length > 0 ? { certBlockedByAbsentInk } : {},
10324
10735
  ...weightGaps.length > 0 ? { fontWeightGaps: weightGaps } : {},
10325
10736
  ...lcdTextEnabled() ? { environmentOverrides: ["enable-lcd-text"] } : {},
10326
- // Run 13 finding 1: the eye-check nudge lived only in the human
10327
- // footer, so MCP-driven agents — the documented path — were never
10328
- // told the check exists and shipped without ever opening a sheet.
10329
- // Structured here so every consumer sees it.
10330
- eyeCheck: {
10331
- command: `tendril inspect "${opts.bundleDir}"`,
10332
- sheetPath: path31.join(opts.bundleDir, "verify-evidence", "inspect.html"),
10333
- 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."
10334
- }
10737
+ eyeCheck: eyeCheck(opts.bundleDir)
10335
10738
  };
10336
10739
  emitData(opts, report, () => {
10337
10740
  for (const s of statuses) {
@@ -10363,11 +10766,10 @@ async function runVerify(opts) {
10363
10766
  process.stdout.write(`UNAVAILABLE composition crops \u2014 ${regionsOut.unavailable}
10364
10767
  `);
10365
10768
  }
10366
- const ic = interactionCoverage(behaviors);
10367
10769
  process.stdout.write(
10368
10770
  `
10369
10771
  ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuses.length} \u2265 pass bar \xB7 behaviors ${behaviors.length - behaviorFailures.length}/${behaviors.length}${checkSummarySegments(
10370
- { availability, structural, crops, occlusion }
10772
+ { availability, structural, crops, occlusion, operability }
10371
10773
  )}
10372
10774
  `
10373
10775
  );
@@ -10396,14 +10798,13 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
10396
10798
  }
10397
10799
  if (evidenceUnverified) {
10398
10800
  process.stdout.write(
10399
- `FAIL interaction-evidence \u2014 the recording proves interactive poses (${interactionEvidence.join(", ")}) and ZERO interaction behaviors were verified. The authoring vocabulary did not map them; this is an instrument failure, not a verified component. Mappable today: ${[...INTERACTION_STATES, ...BOOLEAN_STATES].join("/")} \u2014 on an axis named State (checked/selected state axes are recorded as evidence but not yet authorable \u2014 widening is on the roadmap; this is not fixable from component code).
10400
- `
10401
- );
10402
- } else if (ic.interactionChecks === 0) {
10403
- process.stdout.write(
10404
- `UNVERIFIED operability \u2014 0 interaction checks were authored for this component; the behaviors above are page-level style hygiene. Nothing here says the component responds to a user.
10801
+ `FAIL interaction-evidence \u2014 the recording proves interactive poses (${(interactionEvidence ?? []).join(", ")}) and ZERO interaction behaviors were verified. The authoring vocabulary did not map them; this is an instrument failure, not a verified component. Mappable today: ${[...INTERACTION_STATES, ...BOOLEAN_STATES].join("/")} \u2014 on an axis named State (checked/selected state axes are recorded as evidence but not yet authorable \u2014 widening is on the roadmap; this is not fixable from component code).
10405
10802
  `
10406
10803
  );
10804
+ } else {
10805
+ const line = operabilityLine(operability);
10806
+ if (line !== void 0) process.stdout.write(`${line}
10807
+ `);
10407
10808
  }
10408
10809
  if (unmappedInteractionEvidence.length > 0) {
10409
10810
  process.stdout.write(
@@ -10425,18 +10826,20 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
10425
10826
  }
10426
10827
  process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
10427
10828
  `);
10428
- const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path31.join(opts.bundleDir, f)).filter((f) => existsSync25(f)).map((f) => readFileSync22(f, "utf8")).join("\n")));
10829
+ const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path33.join(opts.bundleDir, f)).filter((f) => existsSync26(f)).map((f) => readFileSync22(f, "utf8")).join("\n")));
10429
10830
  if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
10430
10831
  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)
10431
10832
  `);
10432
10833
  }
10433
10834
  for (const g of weightGaps) {
10434
- process.stdout.write(`fonts (weight gap): "${g.family}" declares ${g.declared.join(", ")} \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (advisory, never a gate). Fix: tendril fonts resolve --set ${task.set}
10435
- `);
10835
+ process.stdout.write(
10836
+ `fonts (weight gap): "${g.family}" declares ${g.declared.join(", ")} \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (advisory, never a gate). Fix: ${tendrilCommand(`fonts resolve --set ${task.set}`)}
10837
+ `
10838
+ );
10436
10839
  }
10437
10840
  process.stdout.write(`evidence: ${evidenceDir} (render/ref/diff per config)
10438
10841
  `);
10439
- process.stdout.write(`eye check: tendril inspect "${opts.bundleDir}" \u2014 magnified recorded-vs-rendered crops of every small node; scores cannot see shape (re-run after every verify)
10842
+ process.stdout.write(`eye check: ${report.eyeCheck.command} \u2014 magnified recorded-vs-rendered crops of every small node; scores cannot see shape (re-run after every verify)
10440
10843
  `);
10441
10844
  });
10442
10845
  if (opts.bar === "cert" && substitutedFamilies.length > 0) {
@@ -10476,9 +10879,9 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
10476
10879
  process.exitCode = ExitCode.VerificationFailed;
10477
10880
  }
10478
10881
  }
10479
- var BARS2, NO_ROLE_MANIFEST, ROLES_NOT_RESOLVED, UNSTAMPED_ROLES;
10882
+ var BARS2, NO_INTERACTIVE_POSES, NO_ROLE_MANIFEST, ROLES_NOT_RESOLVED, UNSTAMPED_ROLES, NO_OVERLAY_DECLARED;
10480
10883
  var init_verify = __esm({
10481
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/verify.ts"() {
10884
+ "packages/cli/src/commands/verify.ts"() {
10482
10885
  "use strict";
10483
10886
  init_src3();
10484
10887
  init_src6();
@@ -10487,11 +10890,13 @@ var init_verify = __esm({
10487
10890
  init_src4();
10488
10891
  init_environment();
10489
10892
  init_font_guidance();
10893
+ init_invocation();
10490
10894
  init_output();
10491
10895
  BARS2 = {
10492
10896
  pass: { sim: 0.95, ink: 0.95 },
10493
10897
  cert: { sim: 0.97, ink: 0.95 }
10494
10898
  };
10899
+ NO_INTERACTIVE_POSES = "no interactive poses recorded \u2014 nothing about focus, typing, or keyboard operation was measured; a component that is not a control passes this field.";
10495
10900
  NO_ROLE_MANIFEST = Object.freeze({
10496
10901
  short: "no role manifest",
10497
10902
  unavailable: "the recording set declares no role manifest \u2014 composition (structural + crop) was NEVER CHECKED; nothing here says the main renders the SHIPPED part modules rather than a pixel-identical re-implementation"
@@ -10501,45 +10906,46 @@ var init_verify = __esm({
10501
10906
  unavailable: "this run never resolved a role manifest \u2014 the legacy --task adapter and pre-manifest reference sets skip the set's roles block entirely, so composition (structural + crop) was NEVER CHECKED and the set was never asked what it declares; nothing here says the main renders the SHIPPED part modules rather than a pixel-identical re-implementation"
10502
10907
  });
10503
10908
  UNSTAMPED_ROLES = "unstamped";
10909
+ NO_OVERLAY_DECLARED = "no overlay declared";
10504
10910
  }
10505
10911
  });
10506
10912
 
10507
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/engine.ts
10913
+ // packages/cli/src/commands/engine.ts
10508
10914
  var engine_exports = {};
10509
10915
  __export(engine_exports, {
10510
10916
  runEngineBrief: () => runEngineBrief,
10511
10917
  runEngineScore: () => runEngineScore
10512
10918
  });
10513
- import { appendFileSync, existsSync as existsSync26, mkdirSync as mkdirSync8, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "node:fs";
10514
- import path32 from "node:path";
10919
+ import { appendFileSync, existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "node:fs";
10920
+ import path34 from "node:path";
10515
10921
  function resolveEngineTask(opts, callerCwd) {
10516
- const asPath = path32.resolve(callerCwd, opts.taskOrSet);
10517
- const isSet = existsSync26(path32.join(asPath, "recording-set.json"));
10922
+ const asPath = path34.resolve(callerCwd, opts.taskOrSet);
10923
+ const isSet = existsSync27(path34.join(asPath, "recording-set.json"));
10518
10924
  const registry = TASKS[opts.taskOrSet];
10519
- if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, disclosures: [], interactionEvidence: [] };
10925
+ if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
10520
10926
  if (isSet) {
10521
10927
  try {
10522
10928
  const authored = authorTaskFromSet(asPath);
10523
10929
  for (const d of authored.disclosures) warn(opts, d);
10524
- return { task: authored.task, name: path32.basename(asPath), disclosures: authored.disclosures, interactionEvidence: authored.api.interactionEvidence, apiPin: { props: authored.api.apiPin.props, forcedStates: authored.api.apiPin.forcedStates } };
10930
+ return { task: authored.task, name: path34.basename(asPath), ref: asPath, disclosures: authored.disclosures, interactionEvidence: authored.api.interactionEvidence, apiPin: { props: authored.api.apiPin.props, forcedStates: authored.api.apiPin.forcedStates } };
10525
10931
  } catch (err) {
10526
10932
  fail(opts, ExitCode.InputValidation, {
10527
10933
  error: `cannot author a task from ${asPath}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
10528
10934
  code: "AUTHORING_FAILED",
10529
- remediation: "Complete the recording set (tendril record status) or use a reference task name."
10935
+ remediation: `Complete the recording set (${tendrilCommand(`record status --set ${asPath}`)}) or use a reference task name.`
10530
10936
  });
10531
10937
  }
10532
10938
  }
10533
10939
  fail(opts, ExitCode.InputValidation, {
10534
10940
  error: `"${opts.taskOrSet}" is neither a reference task nor a recorded set`,
10535
10941
  code: "UNKNOWN_RECORDED_INPUT",
10536
- remediation: `Pick a task (${Object.keys(TASKS).join(", ")}) or point at a set recorded with \`tendril record\`.`
10942
+ remediation: `Pick a task (${Object.keys(TASKS).join(", ")}) or point at a set recorded with \`${tendrilCommand("record plan")}\`.`
10537
10943
  });
10538
10944
  }
10539
10945
  function runEngineBrief(opts) {
10540
10946
  requireEntitlement(opts);
10541
10947
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
10542
- const { task, name, disclosures } = resolveEngineTask(opts, callerCwd);
10948
+ const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
10543
10949
  const bar = BARS3[opts.bar];
10544
10950
  const disclosureBlock = disclosures.length > 0 ? `
10545
10951
 
@@ -10548,8 +10954,8 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
10548
10954
  const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock;
10549
10955
  const segments = buildSegments(task, "files");
10550
10956
  let notRecorded;
10551
- const manifestPath2 = path32.join(task.set, "recording-set.json");
10552
- if (existsSync26(manifestPath2)) {
10957
+ const manifestPath2 = path34.join(task.set, "recording-set.json");
10958
+ if (existsSync27(manifestPath2)) {
10553
10959
  notRecorded = JSON.parse(readFileSync23(manifestPath2, "utf8")).notRecorded;
10554
10960
  }
10555
10961
  const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
@@ -10558,7 +10964,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
10558
10964
  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.
10559
10965
  ${notRecorded}` : "";
10560
10966
  let fontProvisioning;
10561
- if (existsSync26(manifestPath2)) {
10967
+ if (existsSync27(manifestPath2)) {
10562
10968
  const missingFams = unprovisionedFamilies(task.set);
10563
10969
  const unprovided = unprovisionedFaces(task.set);
10564
10970
  const weightOnly = missingFams.length === 0;
@@ -10566,16 +10972,17 @@ ${notRecorded}` : "";
10566
10972
  const names = unprovided.map((f) => `'${f}'`).join(", ");
10567
10973
  const PROPRIETARY = /^(sf pro|sf compact|sf mono|new york|pingfang|segoe ui|helvetica neue|proxima nova|avenir)/i;
10568
10974
  const allProprietary = unprovided.every((f) => PROPRIETARY.test(f.trim()));
10975
+ const resolveCommand = tendrilCommand(`fonts resolve --set ${task.set}`);
10569
10976
  fontProvisioning = {
10570
10977
  unprovided,
10571
10978
  question: {
10572
10979
  prompt: weightOnly ? `This design implies font ${unprovided.length === 1 ? "weight" : "weights"} the local kit lacks: ${names} (the ${missingFams.length === 0 && unprovided.length === 1 ? "family itself is" : "families themselves are"} provisioned). How should it be handled?` : `This design's text uses ${names}, which is not in the local font kit. How should it be handled?`,
10573
10980
  options: [
10574
- allProprietary && !weightOnly ? `(Recommended) ${names} ${unprovided.length === 1 ? "is a proprietary face" : "are proprietary faces"} \u2014 Google Fonts cannot serve ${unprovided.length === 1 ? "it" : "them"}, so skip \`fonts resolve\`: run \`tendril fonts add "<Family>" <weight> <file>\` with the .woff2/.ttf/.otf you hold a licence for (the file never leaves this machine), then re-run this brief \u2014 exact text fidelity.` : `(Recommended) Run \`tendril fonts resolve --set ${task.set}\` \u2014 open-source families are fetched automatically. Any face that fails there is one you license privately: run \`tendril fonts add "<Family>" <weight> <file>\` with the .woff2/.ttf/.otf you hold (the file never leaves this machine). Then re-run this brief \u2014 exact text fidelity.`,
10981
+ allProprietary && !weightOnly ? `(Recommended) ${names} ${unprovided.length === 1 ? "is a proprietary face" : "are proprietary faces"} \u2014 Google Fonts cannot serve ${unprovided.length === 1 ? "it" : "them"}, so skip \`fonts resolve\`: run \`${tendrilCommand('fonts add "<Family>" <weight> <file>')}\` with the .woff2/.ttf/.otf you hold a licence for (the file never leaves this machine), then re-run this brief \u2014 exact text fidelity.` : `(Recommended) Run \`${resolveCommand}\` \u2014 open-source families are fetched automatically. Any face that fails there is one you license privately: run \`${tendrilCommand('fonts add "<Family>" <weight> <file>')}\` with the .woff2/.ttf/.otf you hold (the file never leaves this machine). Then re-run this brief \u2014 exact text fidelity.`,
10575
10982
  weightOnly ? "Continue with nearest-weight rendering: the family is provisioned, certification remains possible, and the gap rides the report as an advisory (fontWeightGaps); resolving removes it." : 'Continue with a substitute face: generation proceeds in a provided family, no config can score "certified" under it (cert-bar runs exit fonts-unproven after their report), the substitution is disclosed, and small text differences are expected and not fixable from CSS.'
10576
10983
  ]
10577
10984
  },
10578
- nonInteractive: weightOnly ? `Run \`tendril fonts resolve --set ${task.set}\` if quick, or continue \u2014 the weight gap is an advisory, never a gate.` : opts.bar === "cert" ? `Resolve the families first (\`tendril fonts resolve --set ${task.set}\`) \u2014 this loop targets the cert bar, and scoring exits fonts-unproven under a substitute, so continuing without resolving is a dead end.` : "Continue with the substitute and state the substitution in your report."
10985
+ nonInteractive: weightOnly ? `Run \`${resolveCommand}\` if quick, or continue \u2014 the weight gap is an advisory, never a gate.` : opts.bar === "cert" ? `Resolve the families first (\`${resolveCommand}\`) \u2014 this loop targets the cert bar, and scoring exits fonts-unproven under a substitute, so continuing without resolving is a dead end.` : "Continue with the substitute and state the substitution in your report."
10579
10986
  };
10580
10987
  }
10581
10988
  }
@@ -10583,8 +10990,8 @@ ${notRecorded}` : "";
10583
10990
 
10584
10991
  === TASK PAYLOAD (recorded truth, verbatim) ===
10585
10992
  ${segments}`;
10586
- const payloadFile = path32.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
10587
- mkdirSync8(path32.dirname(payloadFile), { recursive: true });
10993
+ const payloadFile = path34.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
10994
+ mkdirSync8(path34.dirname(payloadFile), { recursive: true });
10588
10995
  writeFileSync12(payloadFile, payload);
10589
10996
  emitData(
10590
10997
  opts,
@@ -10613,7 +11020,7 @@ ${segments}`;
10613
11020
  "Settle the proposer model (see modelSelection \u2014 ask the user when one is present), then:",
10614
11021
  `Read ${payloadFile} completely \u2014 it is the system brief plus every recorded config's emission, box, and assets.`,
10615
11022
  `Write the complete bundle files (${task.entry}, styles.css, optional tokens.css) into a candidate directory.`,
10616
- `Run \`tendril engine score ${name} <candidateDir> --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"} --json\` \u2014 the CLI is the sole judge, and it refuses to score an undeclared model.`,
11023
+ `Run \`${tendrilCommand(`engine score ${ref} <candidateDir> --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"} --json`)}\` \u2014 the CLI is the sole judge, and it refuses to score an undeclared model.`,
10617
11024
  "Apply the returned feedback and re-score. Stop when all checks pass or two consecutive scores fail to improve.",
10618
11025
  "Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
10619
11026
  ]
@@ -10626,18 +11033,23 @@ ${segments}`;
10626
11033
  }
10627
11034
  );
10628
11035
  }
11036
+ function appendScoreHistory(candidateDir, entry) {
11037
+ appendFileSync(path34.join(candidateDir, "score-history.jsonl"), `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry })}
11038
+ `);
11039
+ }
10629
11040
  async function runEngineScore(opts) {
10630
11041
  requireEntitlement(opts);
10631
11042
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
10632
- const candidateDir = path32.resolve(callerCwd, opts.candidateDir);
11043
+ const candidateDir = path34.resolve(callerCwd, opts.candidateDir);
10633
11044
  const { task, name, apiPin, interactionEvidence } = resolveEngineTask(opts, callerCwd);
10634
- if (!existsSync26(candidateDir)) {
11045
+ if (!existsSync27(candidateDir)) {
10635
11046
  fail(opts, ExitCode.InputValidation, {
10636
11047
  error: `candidate directory not found: ${candidateDir}`,
10637
11048
  code: "candidate-missing",
10638
11049
  remediation: `Write ${task.entry} and styles.css into a directory and pass it.`
10639
11050
  });
10640
11051
  }
11052
+ appendScoreHistory(candidateDir, { event: "round-start", bar: opts.bar });
10641
11053
  if (!fontsResolved()) {
10642
11054
  fail(opts, ExitCode.FontsUnproven, {
10643
11055
  error: "resolved font cache absent \u2014 scoring under system font substitution is refused",
@@ -10655,10 +11067,10 @@ async function runEngineScore(opts) {
10655
11067
  for (const g of missingWeights(task.set)) {
10656
11068
  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)`);
10657
11069
  }
10658
- if (opts.rebind !== true && existsSync26(path32.join(candidateDir, "component.json"))) {
11070
+ if (opts.rebind !== true && existsSync27(path34.join(candidateDir, "component.json"))) {
10659
11071
  const prior = (() => {
10660
11072
  try {
10661
- const read = readBundleManifest(readFileSync23(path32.join(candidateDir, "component.json"), "utf8"));
11073
+ const read = readBundleManifest(readFileSync23(path34.join(candidateDir, "component.json"), "utf8"));
10662
11074
  return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
10663
11075
  } catch {
10664
11076
  return { unreadable: true };
@@ -10680,7 +11092,7 @@ async function runEngineScore(opts) {
10680
11092
  }
10681
11093
  }
10682
11094
  const bar = BARS3[opts.bar];
10683
- const evidenceDir = path32.join(candidateDir, "verify-evidence");
11095
+ const evidenceDir = path34.join(candidateDir, "verify-evidence");
10684
11096
  const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
10685
11097
  const parity = await checkHoverParity(task, candidateDir);
10686
11098
  const behaviors = [...await checkBehaviors(task, candidateDir), ...parity];
@@ -10723,11 +11135,10 @@ METRIC DEADBAND (read before iterating on near-misses): the scored similarity/in
10723
11135
  environment: environmentStamp(taskFontFamilies(task.set)),
10724
11136
  substitutedFamilies
10725
11137
  });
10726
- appendFileSync(
10727
- path32.join(candidateDir, "score-history.jsonl"),
10728
- `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), bar: opts.bar, pass: obj[0], total, certified: certifiedReps.length, floor: obj[1], mean: obj[2] })}
10729
- `
10730
- );
11138
+ appendScoreHistory(candidateDir, { event: "round-scored", bar: opts.bar, pass: obj[0], total, certified: certifiedReps.length, floor: obj[1], mean: obj[2] });
11139
+ const coverage = interactionCoverage(behaviors);
11140
+ const operability = operabilityReport({ behaviors, interactionEvidence });
11141
+ const evidenceUnverified = (interactionEvidence?.length ?? 0) > 0 && coverage.interactionChecks === 0;
10731
11142
  emitData(
10732
11143
  opts,
10733
11144
  {
@@ -10741,7 +11152,14 @@ METRIC DEADBAND (read before iterating on near-misses): the scored similarity/in
10741
11152
  // The loop's oracle must say what verify says: without this an
10742
11153
  // MCP-driven agent literally cannot tell a hollow verdict (all
10743
11154
  // behaviour passes are prelude hygiene) from a verified one.
10744
- coverage: interactionCoverage(behaviors),
11155
+ //
11156
+ // `operability` alone is ONE WORD covering four different states,
11157
+ // and this is the channel where run 15's miss happened: the agent
11158
+ // driving the loop reads this JSON and nothing else. It gets the
11159
+ // same sentence verify's report carries, from the same function —
11160
+ // fixing verify's channel and not this one would have left the
11161
+ // reader who actually acts on it exactly where they were.
11162
+ coverage: { ...coverage, operabilityCheck: operability },
10745
11163
  parityCoverage,
10746
11164
  evidenceDir,
10747
11165
  bundleManifest: emitted.written[0],
@@ -10752,7 +11170,7 @@ METRIC DEADBAND (read before iterating on near-misses): the scored similarity/in
10752
11170
  // Run 11: generators read allPass:true and reported success on
10753
11171
  // bundles verify then FAILED on the interaction-evidence gate —
10754
11172
  // the oracle must say what verify will say, including this.
10755
- ...interactionEvidence.length > 0 && interactionCoverage(behaviors).interactionChecks === 0 ? { interactionEvidenceUnverified: true, verifyWillFail: "interaction-evidence \u2014 the recording proves interactive poses none of the authored behaviors cover; not fixable from component code; REPORT it, do not iterate on it" } : {},
11173
+ ...evidenceUnverified ? { interactionEvidenceUnverified: true, verifyWillFail: "interaction-evidence \u2014 the recording proves interactive poses none of the authored behaviors cover; not fixable from component code; REPORT it, do not iterate on it" } : {},
10756
11174
  certification: { certified: certifiedReps.length, total: scores.length, bar: certBar, note: "tierOf on exact values + parity and absent-ink demotion; verify's composition checks can demote further" }
10757
11175
  },
10758
11176
  () => {
@@ -10763,12 +11181,12 @@ METRIC DEADBAND (read before iterating on near-misses): the scored similarity/in
10763
11181
  process.stdout.write(`
10764
11182
  ${obj[0]}/${total} \xB7 certified ${certifiedReps.length}/${total} \xB7 floor=${obj[1].toFixed(3)} \xB7 mean=${obj[2].toFixed(3)} \xB7 evidence: ${evidenceDir}
10765
11183
  `);
10766
- const ic = interactionCoverage(behaviors);
10767
- if (interactionEvidence.length > 0 && ic.interactionChecks === 0) {
10768
- process.stdout.write(`INSTRUMENT GAP \u2014 the recording proves interactive poses (${interactionEvidence.join(", ")}) and zero interaction behaviors were authored; verify WILL fail this bundle (interaction-evidence). Not fixable from component code \u2014 report it, do not iterate on it.
11184
+ if (evidenceUnverified) {
11185
+ process.stdout.write(`INSTRUMENT GAP \u2014 the recording proves interactive poses (${(interactionEvidence ?? []).join(", ")}) and zero interaction behaviors were authored; verify WILL fail this bundle (interaction-evidence). Not fixable from component code \u2014 report it, do not iterate on it.
10769
11186
  `);
10770
- } else if (ic.interactionChecks === 0) {
10771
- process.stdout.write(`UNVERIFIED operability \u2014 0 interaction checks; the behaviour passes above are page-level style hygiene
11187
+ } else {
11188
+ const line = operabilityLine(operability);
11189
+ if (line !== void 0) process.stdout.write(`${line}
10772
11190
  `);
10773
11191
  }
10774
11192
  }
@@ -10793,7 +11211,7 @@ ${obj[0]}/${total} \xB7 certified ${certifiedReps.length}/${total} \xB7 floor=${
10793
11211
  }
10794
11212
  var BARS3;
10795
11213
  var init_engine2 = __esm({
10796
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/engine.ts"() {
11214
+ "packages/cli/src/commands/engine.ts"() {
10797
11215
  "use strict";
10798
11216
  init_src3();
10799
11217
  init_src7();
@@ -10802,6 +11220,7 @@ var init_engine2 = __esm({
10802
11220
  init_font_guidance();
10803
11221
  init_src4();
10804
11222
  init_output();
11223
+ init_invocation();
10805
11224
  init_entitlement();
10806
11225
  init_verify();
10807
11226
  init_src4();
@@ -10812,16 +11231,16 @@ var init_engine2 = __esm({
10812
11231
  }
10813
11232
  });
10814
11233
 
10815
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/codeconnect.ts
11234
+ // packages/cli/src/commands/codeconnect.ts
10816
11235
  var codeconnect_exports = {};
10817
11236
  __export(codeconnect_exports, {
10818
11237
  runCodeConnect: () => runCodeConnect
10819
11238
  });
10820
- import { existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync13 } from "node:fs";
10821
- import path33 from "node:path";
11239
+ import { existsSync as existsSync28, readFileSync as readFileSync24, writeFileSync as writeFileSync13 } from "node:fs";
11240
+ import path35 from "node:path";
10822
11241
  function runCodeConnect(opts) {
10823
11242
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
10824
- const bundleDir = path33.resolve(callerCwd, opts.bundleDir);
11243
+ const bundleDir = path35.resolve(callerCwd, opts.bundleDir);
10825
11244
  let url;
10826
11245
  try {
10827
11246
  url = new URL(opts.figmaUrl);
@@ -10837,7 +11256,7 @@ function runCodeConnect(opts) {
10837
11256
  }
10838
11257
  let manifest;
10839
11258
  try {
10840
- const read = readBundleManifest(readFileSync24(path33.join(bundleDir, "component.json"), "utf8"));
11259
+ const read = readBundleManifest(readFileSync24(path35.join(bundleDir, "component.json"), "utf8"));
10841
11260
  if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
10842
11261
  manifest = read.manifest;
10843
11262
  } catch (err) {
@@ -10847,8 +11266,8 @@ function runCodeConnect(opts) {
10847
11266
  remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
10848
11267
  });
10849
11268
  }
10850
- const setDir = path33.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
10851
- if (!existsSync27(path33.join(setDir, "recording-set.json"))) {
11269
+ const setDir = path35.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
11270
+ if (!existsSync28(path35.join(setDir, "recording-set.json"))) {
10852
11271
  fail(opts, ExitCode.InputValidation, {
10853
11272
  error: `recording set not found at ${setDir}`,
10854
11273
  code: "codeconnect-no-set",
@@ -10862,15 +11281,15 @@ function runCodeConnect(opts) {
10862
11281
  fail(opts, ExitCode.InputValidation, {
10863
11282
  error: `cannot author the API from ${setDir}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
10864
11283
  code: "codeconnect-authoring-failed",
10865
- remediation: "The set must be a complete protocol recording (tendril record status)."
11284
+ remediation: `The set must be a complete protocol recording (${tendrilCommand(`record status --set ${setDir}`)}).`
10866
11285
  });
10867
11286
  }
10868
11287
  const api = authored.api;
10869
11288
  const component = api.component;
10870
11289
  const recManifest = loadManifest(setDir);
10871
11290
  const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
10872
- const meta = path33.join(setDir, r.slug, "get_metadata.json");
10873
- if (!existsSync27(meta)) return void 0;
11291
+ const meta = path35.join(setDir, r.slug, "get_metadata.json");
11292
+ if (!existsSync28(meta)) return void 0;
10874
11293
  try {
10875
11294
  return /name="([^"]*)"/.exec(envelopeFirstTextPart(JSON.parse(readFileSync24(meta, "utf8"))))?.[1];
10876
11295
  } catch {
@@ -10889,7 +11308,7 @@ function runCodeConnect(opts) {
10889
11308
  fail(opts, ExitCode.InputValidation, {
10890
11309
  error: "the recording set's content no longer matches the hash this bundle was scored against \u2014 its verification claims describe a different recording",
10891
11310
  code: "codeconnect-set-drift",
10892
- remediation: "Re-run `tendril verify` (or engine score) against the current set, then re-emit."
11311
+ remediation: `Re-run \`${tendrilCommand(`verify ${bundleDir} --set ${setDir}`)}\` (or engine score) against the current set, then re-emit.`
10893
11312
  });
10894
11313
  }
10895
11314
  const boolProps = api.props.filter((p) => p.kind === "boolean");
@@ -10930,7 +11349,7 @@ function runCodeConnect(opts) {
10930
11349
  axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
10931
11350
  fragmentVars.push(varName);
10932
11351
  }
10933
- const entryRel = path33.relative(callerCwd, path33.join(bundleDir, manifest.entry));
11352
+ const entryRel = path35.relative(callerCwd, path35.join(bundleDir, manifest.entry));
10934
11353
  const trust = manifest.trustStatement.split("\n")[0] ?? "";
10935
11354
  const lines = [
10936
11355
  `// url=${opts.figmaUrl}`,
@@ -10951,7 +11370,7 @@ function runCodeConnect(opts) {
10951
11370
  `}`,
10952
11371
  ``
10953
11372
  ].join("\n");
10954
- const outFile = path33.resolve(callerCwd, opts.out ?? path33.join(bundleDir, `${component}.figma.ts`));
11373
+ const outFile = path35.resolve(callerCwd, opts.out ?? path35.join(bundleDir, `${component}.figma.ts`));
10955
11374
  writeFileSync13(outFile, lines);
10956
11375
  emitData(
10957
11376
  opts,
@@ -10976,48 +11395,49 @@ function runCodeConnect(opts) {
10976
11395
  }
10977
11396
  var kebab4, q;
10978
11397
  var init_codeconnect = __esm({
10979
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/codeconnect.ts"() {
11398
+ "packages/cli/src/commands/codeconnect.ts"() {
10980
11399
  "use strict";
10981
11400
  init_src3();
10982
11401
  init_src7();
10983
11402
  init_src();
10984
11403
  init_src6();
11404
+ init_invocation();
10985
11405
  init_output();
10986
11406
  kebab4 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
10987
11407
  q = (s) => `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}'`;
10988
11408
  }
10989
11409
  });
10990
11410
 
10991
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/mcp/src/server.ts
11411
+ // packages/mcp/src/server.ts
10992
11412
  import { createHash as createHash5 } from "node:crypto";
10993
- import { existsSync as existsSync28, mkdtempSync as mkdtempSync3, readFileSync as readFileSync25, readdirSync as readdirSync8, writeFileSync as writeFileSync14 } from "node:fs";
11413
+ import { existsSync as existsSync29, mkdtempSync as mkdtempSync3, readFileSync as readFileSync25, readdirSync as readdirSync8, writeFileSync as writeFileSync14 } from "node:fs";
10994
11414
  import os6 from "node:os";
10995
- import path34 from "node:path";
10996
- import { fileURLToPath as fileURLToPath5 } from "node:url";
11415
+ import path36 from "node:path";
11416
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
10997
11417
  import { z as z12 } from "zod";
10998
11418
  function sourceHash() {
10999
- const dir = path34.dirname(fileURLToPath5(import.meta.url));
11419
+ const dir = path36.dirname(fileURLToPath6(import.meta.url));
11000
11420
  const h = createHash5("sha256");
11001
11421
  for (const f of readdirSync8(dir).filter((n) => n.endsWith(".ts")).sort()) {
11002
11422
  h.update(f);
11003
- h.update(readFileSync25(path34.join(dir, f)));
11423
+ h.update(readFileSync25(path36.join(dir, f)));
11004
11424
  }
11005
11425
  return h.digest("hex").slice(0, 16);
11006
11426
  }
11007
11427
  var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
11008
11428
  var init_server = __esm({
11009
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/mcp/src/server.ts"() {
11429
+ "packages/mcp/src/server.ts"() {
11010
11430
  "use strict";
11011
- REPO_ROOT3 = path34.resolve(path34.dirname(fileURLToPath5(import.meta.url)), "..", "..", "..");
11012
- CLI_BIN = path34.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
11013
- BUNDLED_CLI = path34.join(path34.dirname(fileURLToPath5(import.meta.url)), "tendril.js");
11014
- CLI_SPAWN = existsSync28(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
11431
+ REPO_ROOT3 = path36.resolve(path36.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
11432
+ CLI_BIN = path36.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
11433
+ BUNDLED_CLI = path36.join(path36.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
11434
+ CLI_SPAWN = existsSync29(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
11015
11435
  str = (d) => z12.string().describe(d);
11016
11436
  optStr = (d) => z12.string().optional().describe(d);
11017
11437
  TOOLS = [
11018
11438
  {
11019
11439
  name: "tendril_record_plan",
11020
- description: "ENTRY POINT for implementing/building a React component from a Figma design or figma.com URL \u2014 start the Tendril pipeline here (after loading the tendril skill, if installed). Plans a recording session: computes the rep queue (anchor + one-factor + conflict crosses) from the verbatim get_metadata response (pass its blocks via metadataParts \u2014 no file to write) and persists the set manifest. Resumes if the set already exists. The output may carry USER QUESTIONS \u2014 defaultsToConfirm (which pose is the component's default) or a multiple-component-sets error (which set to record): render them to a present user and apply the answers via `defaults` / `componentSet`; non-interactive runs follow each question's stated fallback. Recording cost is stated in figmaCallEstimate, never asked about \u2014 proceed with what the user provided.",
11440
+ description: "ENTRY POINT for implementing/building a React component from a Figma design or figma.com URL \u2014 start the Tendril pipeline here (after loading the tendril skill, if installed). Plans a recording session: computes the rep queue (anchor + one-factor + conflict crosses) from the verbatim get_metadata response (pass its blocks via metadataParts \u2014 no file to write) and persists the set manifest. Resumes if the set already exists. The output may carry USER QUESTIONS \u2014 defaultsToConfirm (which pose is the component's default) or a multiple-component-sets error (which set to record): render them to a present user and apply the answers via `defaults` / `componentSet`; non-interactive runs follow each question's stated fallback. It may also carry `interactionStatesToConfirm` \u2014 the recording holds no hover/focus/pressed state, so nothing shows how the component behaves when someone uses it: say its `statement` and `designFix` in that SAME one message (the fix is a Figma variant, not code). It is a disclosure, not a gate \u2014 no answer is required and recording proceeds regardless. The output also carries `feasibilityCheck`: the call arithmetic for this queue plus the free `whoami` check that turns it into a verdict \u2014 complete that handshake BEFORE the first recording call, and surface the verdict to the user when the set does not fit their daily allowance.",
11021
11441
  schema: z12.object({
11022
11442
  setDir: str("recording set directory to create/resume"),
11023
11443
  component: str("component/system name"),
@@ -11041,7 +11461,7 @@ var init_server = __esm({
11041
11461
  const single = i["metadata"];
11042
11462
  const parts = i["metadataParts"];
11043
11463
  if (single !== void 0 || parts !== void 0) {
11044
- const tmp = path34.join(mkdtempSync3(path34.join(os6.tmpdir(), "tendril-envelope-")), "response.txt");
11464
+ const tmp = path36.join(mkdtempSync3(path36.join(os6.tmpdir(), "tendril-envelope-")), "response.txt");
11045
11465
  if (single !== void 0) {
11046
11466
  writeFileSync14(tmp, single);
11047
11467
  argvOut.push("--metadata-raw-file", tmp);
@@ -11112,7 +11532,7 @@ var init_server = __esm({
11112
11532
  const bridge = (label, single, parts) => {
11113
11533
  if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
11114
11534
  if (single === void 0 && parts === void 0) return;
11115
- const tmp = path34.join(mkdtempSync3(path34.join(os6.tmpdir(), "tendril-envelope-")), "response.txt");
11535
+ const tmp = path36.join(mkdtempSync3(path36.join(os6.tmpdir(), "tendril-envelope-")), "response.txt");
11116
11536
  if (single !== void 0) {
11117
11537
  writeFileSync14(tmp, single);
11118
11538
  argvOut.push(`--${label}-file`, tmp);
@@ -11155,7 +11575,7 @@ var init_server = __esm({
11155
11575
  const file = i["file"];
11156
11576
  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)");
11157
11577
  if (file !== void 0) return [...base, "--file", file];
11158
- const tmp = path34.join(mkdtempSync3(path34.join(os6.tmpdir(), "tendril-envelope-")), "response.txt");
11578
+ const tmp = path36.join(mkdtempSync3(path36.join(os6.tmpdir(), "tendril-envelope-")), "response.txt");
11159
11579
  if (text !== void 0) {
11160
11580
  writeFileSync14(tmp, text);
11161
11581
  return [...base, "--file", tmp, "--raw"];
@@ -11302,7 +11722,7 @@ var init_server = __esm({
11302
11722
  }
11303
11723
  });
11304
11724
 
11305
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/permissions.ts
11725
+ // packages/cli/src/commands/permissions.ts
11306
11726
  var permissions_exports = {};
11307
11727
  __export(permissions_exports, {
11308
11728
  PERMISSIONS_DESCRIPTION: () => PERMISSIONS_DESCRIPTION,
@@ -11310,12 +11730,12 @@ __export(permissions_exports, {
11310
11730
  mergeAllowlist: () => mergeAllowlist,
11311
11731
  runPermissions: () => runPermissions
11312
11732
  });
11313
- import { existsSync as existsSync29, mkdirSync as mkdirSync9, readFileSync as readFileSync26, writeFileSync as writeFileSync15 } from "node:fs";
11733
+ import { existsSync as existsSync30, mkdirSync as mkdirSync9, readFileSync as readFileSync26, writeFileSync as writeFileSync15 } from "node:fs";
11314
11734
  import os7 from "node:os";
11315
- import path35 from "node:path";
11735
+ import path37 from "node:path";
11316
11736
  function mergeAllowlist(file, entries) {
11317
11737
  let settings = {};
11318
- if (existsSync29(file) && readFileSync26(file, "utf8").trim() !== "") {
11738
+ if (existsSync30(file) && readFileSync26(file, "utf8").trim() !== "") {
11319
11739
  settings = JSON.parse(readFileSync26(file, "utf8"));
11320
11740
  if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
11321
11741
  }
@@ -11328,7 +11748,7 @@ function mergeAllowlist(file, entries) {
11328
11748
  const alreadyPresent = entries.filter((e) => present.has(e));
11329
11749
  if (added.length > 0) {
11330
11750
  allow.push(...added);
11331
- mkdirSync9(path35.dirname(file), { recursive: true });
11751
+ mkdirSync9(path37.dirname(file), { recursive: true });
11332
11752
  writeFileSync15(file, `${JSON.stringify(settings, null, 2)}
11333
11753
  `);
11334
11754
  }
@@ -11364,7 +11784,7 @@ async function runPermissions(flags) {
11364
11784
  const result = await buildPermissions({ ...flags.mcpUrl ? { mcpUrl: flags.mcpUrl } : {} });
11365
11785
  if (flags.write) {
11366
11786
  const base = process.env["INIT_CWD"] ?? process.cwd();
11367
- const file = flags.user ? path35.join(os7.homedir(), ".claude", "settings.json") : path35.join(base, ".claude", "settings.local.json");
11787
+ const file = flags.user ? path37.join(os7.homedir(), ".claude", "settings.json") : path37.join(base, ".claude", "settings.local.json");
11368
11788
  if (flags.dryRun) {
11369
11789
  emitData(flags, { file, wouldAdd: result.toolEntries }, () => {
11370
11790
  process.stdout.write(`dry-run: would merge ${result.toolEntries.length} per-tool entries into ${file}
@@ -11397,7 +11817,7 @@ async function runPermissions(flags) {
11397
11817
  `Claude Code allowlist for the Tendril pipeline.
11398
11818
  One command installs it (project-local, idempotent):
11399
11819
 
11400
- tendril permissions --claude --write
11820
+ ${tendrilCommand("permissions --claude --write")}
11401
11821
 
11402
11822
  Or paste into .claude/settings.json under permissions.allow:
11403
11823
 
@@ -11416,12 +11836,13 @@ ${result.directConfigNote}
11416
11836
  }
11417
11837
  var FIGMA_TOOL_FALLBACK, PERMISSIONS_DESCRIPTION, TENDRIL_PLUGIN_PREFIX, FIGMA_PLUGIN_PREFIX;
11418
11838
  var init_permissions = __esm({
11419
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/permissions.ts"() {
11839
+ "packages/cli/src/commands/permissions.ts"() {
11420
11840
  "use strict";
11421
11841
  init_src3();
11422
11842
  init_src();
11423
11843
  init_server();
11424
11844
  init_describe();
11845
+ init_invocation();
11425
11846
  init_output();
11426
11847
  init_doctor();
11427
11848
  FIGMA_TOOL_FALLBACK = ["get_metadata", "get_design_context", "get_screenshot", "get_variable_defs", "get_motion_context", "get_figjam"];
@@ -11451,27 +11872,27 @@ var init_permissions = __esm({
11451
11872
  }
11452
11873
  });
11453
11874
 
11454
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/inspect.ts
11875
+ // packages/cli/src/commands/inspect.ts
11455
11876
  var inspect_exports = {};
11456
11877
  __export(inspect_exports, {
11457
11878
  INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
11458
11879
  runInspect: () => runInspect
11459
11880
  });
11460
- import { existsSync as existsSync30, readFileSync as readFileSync27, writeFileSync as writeFileSync16 } from "node:fs";
11461
- import path36 from "node:path";
11881
+ import { existsSync as existsSync31, readFileSync as readFileSync27, writeFileSync as writeFileSync16 } from "node:fs";
11882
+ import path38 from "node:path";
11462
11883
  async function runInspect(opts) {
11463
11884
  if (opts.describe) {
11464
11885
  printDescription(INSPECT_DESCRIPTION);
11465
11886
  return;
11466
11887
  }
11467
- const bundleDir = path36.resolve(opts.bundleDir);
11468
- const evidenceDir = path36.join(bundleDir, "verify-evidence");
11469
- const manifestPath2 = path36.join(bundleDir, "component.json");
11470
- if (!existsSync30(evidenceDir) || !existsSync30(manifestPath2)) {
11888
+ const bundleDir = path38.resolve(opts.bundleDir);
11889
+ const evidenceDir = path38.join(bundleDir, "verify-evidence");
11890
+ const manifestPath2 = path38.join(bundleDir, "component.json");
11891
+ if (!existsSync31(evidenceDir) || !existsSync31(manifestPath2)) {
11471
11892
  fail(opts, ExitCode.InputValidation, {
11472
- error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync30(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
11893
+ error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync31(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
11473
11894
  code: "no-evidence",
11474
- remediation: "Run `tendril verify <bundleDir>` first \u2014 inspect reads the ref/render evidence that run writes."
11895
+ remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
11475
11896
  });
11476
11897
  }
11477
11898
  const { manifest } = readBundleManifest(readFileSync27(manifestPath2, "utf8"));
@@ -11482,27 +11903,27 @@ async function runInspect(opts) {
11482
11903
  remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
11483
11904
  });
11484
11905
  }
11485
- const setDir = path36.resolve(opts.set ?? manifest.provenance.recordingSet.path);
11486
- const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync30(path36.join(evidenceDir, `${rep}-ref.png`)) && existsSync30(path36.join(evidenceDir, `${rep}-render.png`)));
11906
+ const setDir = path38.resolve(opts.set ?? manifest.provenance.recordingSet.path);
11907
+ const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync31(path38.join(evidenceDir, `${rep}-ref.png`)) && existsSync31(path38.join(evidenceDir, `${rep}-render.png`)));
11487
11908
  if (reps.length === 0) {
11488
11909
  fail(opts, ExitCode.InputValidation, {
11489
11910
  error: "verify-evidence holds no ref/render pairs for this bundle's configs",
11490
11911
  code: "no-evidence",
11491
- remediation: "Run `tendril verify <bundleDir>` first \u2014 inspect reads the ref/render evidence that run writes."
11912
+ remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
11492
11913
  });
11493
11914
  }
11494
11915
  let crops = 0;
11495
11916
  const sections = [];
11496
11917
  for (const rep of reps) {
11497
- const ref = new Uint8Array(readFileSync27(path36.join(evidenceDir, `${rep}-ref.png`)));
11498
- const render = new Uint8Array(readFileSync27(path36.join(evidenceDir, `${rep}-render.png`)));
11918
+ const ref = new Uint8Array(readFileSync27(path38.join(evidenceDir, `${rep}-ref.png`)));
11919
+ const render = new Uint8Array(readFileSync27(path38.join(evidenceDir, `${rep}-render.png`)));
11499
11920
  const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
11500
11921
  const cells = [];
11501
11922
  for (const [i, n] of nodes.entries()) {
11502
11923
  const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
11503
11924
  try {
11504
- writeFileSync16(path36.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
11505
- writeFileSync16(path36.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
11925
+ writeFileSync16(path38.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
11926
+ writeFileSync16(path38.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
11506
11927
  } catch {
11507
11928
  continue;
11508
11929
  }
@@ -11515,7 +11936,7 @@ async function runInspect(opts) {
11515
11936
  `<section><h2>${esc(rep)}</h2><div class="full"><span><em>recorded</em><img src="./${rep}-ref.png"></span><span><em>rendered</em><img src="./${rep}-render.png"></span><span><em>diff</em><img src="./${rep}-diff.png"></span></div>` + (cells.length > 0 ? `<div class="grid">${cells.join("")}</div>` : `<p class="none">no small recorded nodes in this config's sweep</p>`) + `</section>`
11516
11937
  );
11517
11938
  }
11518
- const sheet = path36.join(evidenceDir, "inspect.html");
11939
+ const sheet = path38.join(evidenceDir, "inspect.html");
11519
11940
  writeFileSync16(
11520
11941
  sheet,
11521
11942
  `<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
@@ -11540,12 +11961,13 @@ ${reps.length} config(s), ${crops} detail crop pair(s) \u2014 open the sheet and
11540
11961
  }
11541
11962
  var INSPECT_DESCRIPTION, esc;
11542
11963
  var init_inspect = __esm({
11543
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/inspect.ts"() {
11964
+ "packages/cli/src/commands/inspect.ts"() {
11544
11965
  "use strict";
11545
11966
  init_src3();
11546
11967
  init_src6();
11547
11968
  init_src4();
11548
11969
  init_describe();
11970
+ init_invocation();
11549
11971
  init_output();
11550
11972
  INSPECT_DESCRIPTION = {
11551
11973
  name: "inspect",
@@ -11568,7 +11990,7 @@ var init_inspect = __esm({
11568
11990
  }
11569
11991
  });
11570
11992
 
11571
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/generate-route.ts
11993
+ // packages/cli/src/commands/generate-route.ts
11572
11994
  var generate_route_exports = {};
11573
11995
  __export(generate_route_exports, {
11574
11996
  routeGenerate: () => routeGenerate
@@ -11581,28 +12003,28 @@ function routeGenerate(input) {
11581
12003
  return { path: "facts", ...input.url !== void 0 ? { url: input.url } : {} };
11582
12004
  }
11583
12005
  var init_generate_route = __esm({
11584
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/generate-route.ts"() {
12006
+ "packages/cli/src/commands/generate-route.ts"() {
11585
12007
  "use strict";
11586
12008
  }
11587
12009
  });
11588
12010
 
11589
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/generate-recorded.ts
12011
+ // packages/cli/src/commands/generate-recorded.ts
11590
12012
  var generate_recorded_exports = {};
11591
12013
  __export(generate_recorded_exports, {
11592
12014
  runGenerateRecorded: () => runGenerateRecorded
11593
12015
  });
11594
12016
  import { confirm as confirm3, isCancel as isCancel3 } from "@clack/prompts";
11595
- import { existsSync as existsSync31, readFileSync as readFileSync28 } from "node:fs";
11596
- import path37 from "node:path";
12017
+ import { existsSync as existsSync32, readFileSync as readFileSync28 } from "node:fs";
12018
+ import path39 from "node:path";
11597
12019
  async function runGenerateRecorded(opts) {
11598
12020
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
11599
- const outDirAbs = path37.resolve(callerCwd, opts.out);
11600
- const recordedAsPath = path37.resolve(callerCwd, opts.recorded);
12021
+ const outDirAbs = path39.resolve(callerCwd, opts.out);
12022
+ const recordedAsPath = path39.resolve(callerCwd, opts.recorded);
11601
12023
  let task;
11602
12024
  let taskName;
11603
12025
  let authoredApi;
11604
12026
  let composition;
11605
- const isSet = existsSync31(path37.join(recordedAsPath, "recording-set.json"));
12027
+ const isSet = existsSync32(path39.join(recordedAsPath, "recording-set.json"));
11606
12028
  const registry = TASKS[opts.recorded];
11607
12029
  if (registry !== void 0 && !isSet) {
11608
12030
  task = registry;
@@ -11611,7 +12033,7 @@ async function runGenerateRecorded(opts) {
11611
12033
  try {
11612
12034
  const authored = authorTaskFromSet(recordedAsPath);
11613
12035
  task = authored.task;
11614
- taskName = path37.basename(recordedAsPath);
12036
+ taskName = path39.basename(recordedAsPath);
11615
12037
  authoredApi = authored.api;
11616
12038
  const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
11617
12039
  if (roles.success) composition = roles.data;
@@ -11620,14 +12042,14 @@ async function runGenerateRecorded(opts) {
11620
12042
  fail(opts, ExitCode.InputValidation, {
11621
12043
  error: `cannot author a task from ${recordedAsPath}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
11622
12044
  code: "AUTHORING_FAILED",
11623
- remediation: "Complete the recording set (tendril record status) or use a reference-corpus task name."
12045
+ remediation: `Complete the recording set (${tendrilCommand(`record status --set ${recordedAsPath}`)}) or use a reference-corpus task name.`
11624
12046
  });
11625
12047
  }
11626
12048
  } else {
11627
12049
  fail(opts, ExitCode.InputValidation, {
11628
12050
  error: `"${opts.recorded}" is neither a reference task nor a recorded set (no recording-set.json found)`,
11629
12051
  code: "UNKNOWN_RECORDED_INPUT",
11630
- remediation: `Pick a task (${Object.keys(TASKS).join(", ")}) or point at a set recorded with \`tendril record\`.`
12052
+ remediation: `Pick a task (${Object.keys(TASKS).join(", ")}) or point at a set recorded with \`${tendrilCommand("record plan")}\`.`
11631
12053
  });
11632
12054
  }
11633
12055
  if (!fontsResolved()) {
@@ -11645,13 +12067,13 @@ async function runGenerateRecorded(opts) {
11645
12067
  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)`);
11646
12068
  }
11647
12069
  const missing = task.configs.filter(
11648
- (c) => !existsSync31(path37.join(task.set, c.rep, "get_screenshot.json")) || !existsSync31(path37.join(task.set, c.rep, "get_metadata.json")) || !existsSync31(path37.join(task.set, c.rep, "get_design_context.json"))
12070
+ (c) => !existsSync32(path39.join(task.set, c.rep, "get_screenshot.json")) || !existsSync32(path39.join(task.set, c.rep, "get_metadata.json")) || !existsSync32(path39.join(task.set, c.rep, "get_design_context.json"))
11649
12071
  );
11650
12072
  if (missing.length > 0) {
11651
12073
  fail(opts, ExitCode.RecordingIncomplete, {
11652
12074
  error: `recording set ${task.set} is missing envelopes for ${missing.length} rep(s): ${missing.slice(0, 3).map((c) => c.rep).join(", ")}${missing.length > 3 ? ", \u2026" : ""}`,
11653
12075
  code: "recording-incomplete",
11654
- remediation: "Resume the recording session (`tendril record next`) until `tendril record status` reports the set complete."
12076
+ remediation: `Resume the recording session (\`${tendrilCommand(`record next --set ${task.set}`)}\`) until \`${tendrilCommand(`record status --set ${task.set}`)}\` reports the set complete.`
11655
12077
  });
11656
12078
  }
11657
12079
  const modelId = opts.model ?? DEFAULT_MODEL_CONFIG.curatedDefault ?? DEFAULT_MODEL_CONFIG.bulk;
@@ -11672,7 +12094,7 @@ async function runGenerateRecorded(opts) {
11672
12094
  fail(opts, ExitCode.Auth, {
11673
12095
  error: "OPENROUTER_API_KEY is not configured \u2014 the curated engine needs an OpenRouter-compatible key.",
11674
12096
  code: "MISSING_OPENROUTER_KEY",
11675
- remediation: "Two paths: run `tendril init` to store your OpenRouter key (BYOK \u2014 your key, your inference), or drive the agent-harness engine explicitly with your own agent host."
12097
+ remediation: `Two paths: run \`${tendrilCommand("init")}\` to store your OpenRouter key (BYOK \u2014 your key, your inference), or drive the agent-harness engine explicitly with your own agent host.`
11676
12098
  });
11677
12099
  }
11678
12100
  const bar = BARS4[opts.bar];
@@ -11715,8 +12137,8 @@ async function runGenerateRecorded(opts) {
11715
12137
  ` : `${line}
11716
12138
  `);
11717
12139
  if (opts.dryRun) {
11718
- emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path37.join(outDirAbs, taskName) }, () => {
11719
- process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path37.join(outDirAbs, taskName)})
12140
+ emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path39.join(outDirAbs, taskName) }, () => {
12141
+ process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path39.join(outDirAbs, taskName)})
11720
12142
  `);
11721
12143
  });
11722
12144
  return;
@@ -11739,10 +12161,10 @@ async function runGenerateRecorded(opts) {
11739
12161
  });
11740
12162
  }
11741
12163
  }
11742
- const bundleDir = path37.join(outDirAbs, taskName);
11743
- if (existsSync31(path37.join(bundleDir, "component.json"))) {
12164
+ const bundleDir = path39.join(outDirAbs, taskName);
12165
+ if (existsSync32(path39.join(bundleDir, "component.json"))) {
11744
12166
  try {
11745
- const prior = readBundleManifest(readFileSync28(path37.join(bundleDir, "component.json"), "utf8")).manifest;
12167
+ const prior = readBundleManifest(readFileSync28(path39.join(bundleDir, "component.json"), "utf8")).manifest;
11746
12168
  if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
11747
12169
  fail(opts, ExitCode.InputValidation, {
11748
12170
  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`,
@@ -11868,7 +12290,7 @@ ${certified}/${statuses.length} certified \xB7 ${statuses.length - pixelFailures
11868
12290
  }
11869
12291
  var BARS4;
11870
12292
  var init_generate_recorded = __esm({
11871
- "../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/generate-recorded.ts"() {
12293
+ "packages/cli/src/commands/generate-recorded.ts"() {
11872
12294
  "use strict";
11873
12295
  init_src3();
11874
12296
  init_src5();
@@ -11879,6 +12301,7 @@ var init_generate_recorded = __esm({
11879
12301
  init_env();
11880
12302
  init_environment();
11881
12303
  init_font_guidance();
12304
+ init_invocation();
11882
12305
  init_output();
11883
12306
  BARS4 = {
11884
12307
  pass: { sim: 0.95, ink: 0.95 },
@@ -11887,24 +12310,27 @@ var init_generate_recorded = __esm({
11887
12310
  }
11888
12311
  });
11889
12312
 
11890
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/bin.ts
12313
+ // packages/cli/src/bin.ts
11891
12314
  init_src3();
12315
+ init_invocation();
11892
12316
  import { CommanderError } from "commander";
11893
12317
 
11894
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/program.ts
12318
+ // packages/cli/src/program.ts
11895
12319
  init_src3();
11896
12320
  init_environment();
12321
+ init_invocation();
11897
12322
  init_doctor();
11898
12323
  import { Command } from "commander";
11899
12324
 
11900
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/init.ts
12325
+ // packages/cli/src/commands/init.ts
11901
12326
  init_src3();
11902
12327
  init_describe();
11903
12328
  init_env();
12329
+ init_invocation();
11904
12330
  init_output();
11905
12331
  import { intro, isCancel, outro, password } from "@clack/prompts";
11906
12332
  import fs from "node:fs";
11907
- import path23 from "node:path";
12333
+ import path24 from "node:path";
11908
12334
  var INIT_DESCRIPTION = {
11909
12335
  name: "init",
11910
12336
  summary: "Configure Figma and OpenRouter credentials in .env (idempotent).",
@@ -11941,7 +12367,7 @@ async function runInit(flags) {
11941
12367
  printDescription(INIT_DESCRIPTION);
11942
12368
  return;
11943
12369
  }
11944
- const envPath = path23.resolve(process.cwd(), ".env");
12370
+ const envPath = path24.resolve(process.cwd(), ".env");
11945
12371
  const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
11946
12372
  let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
11947
12373
  let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
@@ -11950,7 +12376,7 @@ async function runInit(flags) {
11950
12376
  fail(flags, ExitCode.InputValidation, {
11951
12377
  error: "Missing credentials and prompts are disabled.",
11952
12378
  code: "MISSING_CREDENTIALS",
11953
- remediation: "Pass --figma-token <token> and --openrouter-key <key>, or run `tendril init` in an interactive terminal."
12379
+ remediation: `Pass --figma-token <token> and --openrouter-key <key>, or run \`${tendrilCommand("init")}\` in an interactive terminal.`
11954
12380
  });
11955
12381
  }
11956
12382
  if (interactive) {
@@ -11962,7 +12388,7 @@ async function runInit(flags) {
11962
12388
  next.set(ENV_KEYS.figma, figmaToken);
11963
12389
  next.set(ENV_KEYS.openrouter, openrouterKey);
11964
12390
  const changed = existing.get(ENV_KEYS.figma) !== next.get(ENV_KEYS.figma) || existing.get(ENV_KEYS.openrouter) !== next.get(ENV_KEYS.openrouter);
11965
- const gitignorePath = path23.resolve(process.cwd(), ".gitignore");
12391
+ const gitignorePath = path24.resolve(process.cwd(), ".gitignore");
11966
12392
  const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
11967
12393
  const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
11968
12394
  if (flags.dryRun) {
@@ -12007,26 +12433,27 @@ async function runInit(flags) {
12007
12433
  });
12008
12434
  }
12009
12435
 
12010
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/generate.ts
12436
+ // packages/cli/src/commands/generate.ts
12011
12437
  init_src3();
12012
12438
  init_src();
12013
12439
  init_src5();
12014
12440
  init_src2();
12015
12441
  init_describe();
12016
12442
  init_env();
12443
+ init_invocation();
12017
12444
  init_output();
12018
12445
  init_entitlement();
12019
12446
  import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
12020
- import { readFileSync as readFileSync15, readdirSync as readdirSync4, existsSync as existsSync18 } from "node:fs";
12447
+ import { readFileSync as readFileSync15, readdirSync as readdirSync4, existsSync as existsSync19 } from "node:fs";
12021
12448
 
12022
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/pipeline.ts
12449
+ // packages/cli/src/pipeline.ts
12023
12450
  init_src2();
12024
12451
  init_src4();
12025
12452
  init_src6();
12026
12453
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
12027
- import path24 from "node:path";
12454
+ import path25 from "node:path";
12028
12455
 
12029
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/assets-module.ts
12456
+ // packages/cli/src/assets-module.ts
12030
12457
  init_src();
12031
12458
  var KEBAB_ATTRS = {
12032
12459
  "fill-rule": "fillRule",
@@ -12137,7 +12564,7 @@ function buildAssetsModule(assets, aliases = {}) {
12137
12564
  return { source, flags };
12138
12565
  }
12139
12566
 
12140
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/pipeline.ts
12567
+ // packages/cli/src/pipeline.ts
12141
12568
  var VerifyFailedError = class extends Error {
12142
12569
  constructor(loop) {
12143
12570
  super(
@@ -12360,7 +12787,7 @@ async function runGenerationPipeline(input) {
12360
12787
  });
12361
12788
  const written = [];
12362
12789
  if (!input.dryRun) {
12363
- const dir = path24.resolve(input.outDir, semantics.componentName);
12790
+ const dir = path25.resolve(input.outDir, semantics.componentName);
12364
12791
  mkdirSync5(dir, { recursive: true });
12365
12792
  const files = {
12366
12793
  // Bundle-local tokens: THE emission the component's CSS resolves
@@ -12384,13 +12811,13 @@ async function runGenerationPipeline(input) {
12384
12811
  `
12385
12812
  };
12386
12813
  for (const [name, content] of Object.entries(files)) {
12387
- const filePath = path24.join(dir, name);
12814
+ const filePath = path25.join(dir, name);
12388
12815
  writeFileSync8(filePath, content);
12389
12816
  written.push(filePath);
12390
12817
  }
12391
12818
  for (const artifact of emitTokenArtifacts(input.mapping)) {
12392
- const filePath = path24.resolve(input.outDir, artifact.path);
12393
- mkdirSync5(path24.dirname(filePath), { recursive: true });
12819
+ const filePath = path25.resolve(input.outDir, artifact.path);
12820
+ mkdirSync5(path25.dirname(filePath), { recursive: true });
12394
12821
  writeFileSync8(filePath, artifact.content);
12395
12822
  written.push(filePath);
12396
12823
  }
@@ -12398,7 +12825,7 @@ async function runGenerationPipeline(input) {
12398
12825
  return { semantics, loop: finalLoop, report, componentJson, usageMd: docs.usageMd, written };
12399
12826
  }
12400
12827
 
12401
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/commands/generate.ts
12828
+ // packages/cli/src/commands/generate.ts
12402
12829
  var TOOL_VERSION = "0.0.0";
12403
12830
  var GENERATE_DESCRIPTION = {
12404
12831
  name: "generate",
@@ -12454,7 +12881,7 @@ function resolveProvidedSource(flags, contextFile) {
12454
12881
  fail(flags, ExitCode.InputValidation, {
12455
12882
  error: `Cannot read context file "${contextFile}".`,
12456
12883
  code: "CONTEXT_UNREADABLE",
12457
- remediation: "Pass a readable JSON file via --context; run `tendril generate --describe` for the payload shape."
12884
+ remediation: `Pass a readable JSON file via --context; run \`${tendrilCommand("generate --describe")}\` for the payload shape.`
12458
12885
  });
12459
12886
  }
12460
12887
  try {
@@ -12463,7 +12890,7 @@ function resolveProvidedSource(flags, contextFile) {
12463
12890
  fail(flags, ExitCode.InputValidation, {
12464
12891
  error: `Context file "${contextFile}" is not a valid design-context payload: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
12465
12892
  code: "CONTEXT_INVALID",
12466
- remediation: "The payload must match { nodeTree, variables[], metadata, screenshotBase64? } \u2014 see `tendril generate --describe` and docs/adr/003-figma-access-strategy.md."
12893
+ remediation: `The payload must match { nodeTree, variables[], metadata, screenshotBase64? } \u2014 see \`${tendrilCommand("generate --describe")}\` and docs/adr/003-figma-access-strategy.md.`
12467
12894
  });
12468
12895
  }
12469
12896
  }
@@ -12473,7 +12900,7 @@ function resolveSource(flags, url) {
12473
12900
  fail(flags, ExitCode.General, {
12474
12901
  error: "Live Figma transports (desktop MCP / REST) are not wired up yet.",
12475
12902
  code: "NOT_IMPLEMENTED",
12476
- remediation: "Use `tendril generate --context <file>` with an agent-fetched payload, or mock://button for the offline fixture; live transports are task 1.2 in docs/PLAN.md."
12903
+ remediation: `Use \`${tendrilCommand("generate --context <file>")}\` with an agent-fetched payload, or mock://button for the offline fixture; live transports are task 1.2 in docs/PLAN.md.`
12477
12904
  });
12478
12905
  }
12479
12906
  fail(flags, ExitCode.InputValidation, {
@@ -12492,7 +12919,7 @@ async function runGenerate(url, flags) {
12492
12919
  fail(flags, ExitCode.InputValidation, {
12493
12920
  error: "Missing input: pass a recording set or reference task (recorded truth, the default), mock://button, or --context <file>.",
12494
12921
  code: "MISSING_ARGUMENT",
12495
- remediation: "Run `tendril generate --describe` to see arguments and examples."
12922
+ remediation: `Run \`${tendrilCommand("generate --describe")}\` to see arguments and examples.`
12496
12923
  });
12497
12924
  }
12498
12925
  url ??= `provided://${flags.context}`;
@@ -12569,10 +12996,10 @@ token mapping (${mapping.flat.length} variables):
12569
12996
  let initialCode;
12570
12997
  let initialSemantics;
12571
12998
  try {
12572
- if (existsSync18(flags.out)) {
12999
+ if (existsSync19(flags.out)) {
12573
13000
  for (const entry of readdirSync4(flags.out)) {
12574
13001
  const cjPath = `${flags.out}/${entry}/component.json`;
12575
- if (!existsSync18(cjPath)) continue;
13002
+ if (!existsSync19(cjPath)) continue;
12576
13003
  const cj = JSON.parse(readFileSync15(cjPath, "utf8"));
12577
13004
  if (cj.name !== void 0 && Array.isArray(cj.props)) {
12578
13005
  previousApi = JSON.stringify({
@@ -12581,13 +13008,13 @@ token mapping (${mapping.flat.length} variables):
12581
13008
  });
12582
13009
  const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
12583
13010
  const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
12584
- if (flags.refine && existsSync18(tsxPath) && existsSync18(cssPath)) {
13011
+ if (flags.refine && existsSync19(tsxPath) && existsSync19(cssPath)) {
12585
13012
  initialCode = {
12586
13013
  tsx: readFileSync15(tsxPath, "utf8"),
12587
13014
  css: readFileSync15(cssPath, "utf8")
12588
13015
  };
12589
13016
  const semPath = `${flags.out}/${entry}/semantics.json`;
12590
- if (existsSync18(semPath)) {
13017
+ if (existsSync19(semPath)) {
12591
13018
  initialSemantics = JSON.parse(readFileSync15(semPath, "utf8"));
12592
13019
  }
12593
13020
  }
@@ -12617,7 +13044,7 @@ token mapping (${mapping.flat.length} variables):
12617
13044
  fail(flags, ExitCode.Auth, {
12618
13045
  error: "OPENROUTER_API_KEY is not configured.",
12619
13046
  code: "MISSING_OPENROUTER_KEY",
12620
- remediation: "Run `tendril init` to store your OpenRouter key in .env (BYOK \u2014 your key, your inference)."
13047
+ remediation: `Run \`${tendrilCommand("init")}\` to store your OpenRouter key in .env (BYOK \u2014 your key, your inference).`
12621
13048
  });
12622
13049
  }
12623
13050
  genModel = new OpenRouterGenerationModel(new OpenRouterClient({ apiKey }), model.id, {
@@ -12726,7 +13153,7 @@ bundle written to ${flags.out}/${result.semantics.componentName}/:
12726
13153
  );
12727
13154
  }
12728
13155
 
12729
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/program.ts
13156
+ // packages/cli/src/program.ts
12730
13157
  init_src4();
12731
13158
  function globalFlags(cmd) {
12732
13159
  const opts = cmd.optsWithGlobals();
@@ -12840,7 +13267,7 @@ function buildProgram() {
12840
13267
  fail2(flags, ExitCode.InputValidation, {
12841
13268
  error: family === void 0 ? "nothing to resolve \u2014 pass a family name or --set <recording-dir>" : "pass a family name OR --set, not both \u2014 --set derives families from the recording itself",
12842
13269
  code: "INPUT_VALIDATION",
12843
- remediation: 'tendril fonts resolve --set <recording-dir>, or tendril fonts resolve "<Family>" --weights 400 500 600'
13270
+ remediation: `${tendrilCommand("fonts resolve --set <recording-dir>")}, or ${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}`
12844
13271
  });
12845
13272
  }
12846
13273
  if (set !== void 0) {
@@ -12983,7 +13410,7 @@ function buildProgram() {
12983
13410
  return program;
12984
13411
  }
12985
13412
 
12986
- // ../../../../../../Users/jordyarnoldussen/Desktop/Tendril/packages/cli/src/bin.ts
13413
+ // packages/cli/src/bin.ts
12987
13414
  try {
12988
13415
  await buildProgram().parseAsync(process.argv);
12989
13416
  } catch (err) {
@@ -12994,7 +13421,7 @@ try {
12994
13421
  const cliError2 = {
12995
13422
  error: err.message,
12996
13423
  code: "INPUT_VALIDATION",
12997
- remediation: "Run `tendril --help` or `<command> --describe` for usage."
13424
+ remediation: `Run \`${tendrilCommand("--help")}\` or \`${tendrilCommand("<command> --describe")}\` for usage.`
12998
13425
  };
12999
13426
  process.stderr.write(`${JSON.stringify(cliError2)}
13000
13427
  `);