@tendrilapp/cli 0.1.31 → 0.1.32

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
@@ -1297,9 +1297,396 @@ var init_session = __esm({
1297
1297
  }
1298
1298
  });
1299
1299
 
1300
- // packages/figma/src/recording/roles.ts
1301
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
1300
+ // packages/figma/src/recording/compose.ts
1301
+ import { createHash } from "node:crypto";
1302
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
1302
1303
  import path2 from "node:path";
1304
+ import { z as z5 } from "zod";
1305
+ function buildComposeIndex(roots, depth = 3) {
1306
+ const found = [];
1307
+ const walk2 = (dir, remaining) => {
1308
+ if (existsSync2(path2.join(dir, "recording-set.json"))) {
1309
+ found.push(dir);
1310
+ return;
1311
+ }
1312
+ if (remaining === 0) return;
1313
+ let entries2;
1314
+ try {
1315
+ entries2 = readdirSync(dir);
1316
+ } catch {
1317
+ return;
1318
+ }
1319
+ for (const e of entries2) {
1320
+ if (e === "node_modules" || e.startsWith(".")) continue;
1321
+ const full = path2.join(dir, e);
1322
+ try {
1323
+ if (statSync(full).isDirectory()) walk2(full, remaining - 1);
1324
+ } catch {
1325
+ }
1326
+ }
1327
+ };
1328
+ for (const r of roots) walk2(path2.resolve(r), depth);
1329
+ const seen = /* @__PURE__ */ new Set();
1330
+ const uniqueDirs = found.filter((d) => {
1331
+ const key = path2.resolve(d);
1332
+ if (seen.has(key)) return false;
1333
+ seen.add(key);
1334
+ return true;
1335
+ });
1336
+ const entries = [];
1337
+ for (const dir of uniqueDirs) {
1338
+ let manifest;
1339
+ try {
1340
+ manifest = loadManifest(dir);
1341
+ } catch {
1342
+ continue;
1343
+ }
1344
+ const ownIdsByRep = /* @__PURE__ */ new Map();
1345
+ const ownIds = /* @__PURE__ */ new Set();
1346
+ const variantNodeIds = /* @__PURE__ */ new Set();
1347
+ const repSlugByVariantNode = /* @__PURE__ */ new Map();
1348
+ for (const rep of manifest.reps) {
1349
+ variantNodeIds.add(rep.nodeId);
1350
+ repSlugByVariantNode.set(rep.nodeId, rep.slug);
1351
+ const metaFile = path2.join(dir, rep.slug, "get_metadata.json");
1352
+ if (!existsSync2(metaFile)) continue;
1353
+ try {
1354
+ const text = envelopeTextContent(JSON.parse(readFileSync2(metaFile, "utf8")));
1355
+ const ids = /* @__PURE__ */ new Set();
1356
+ const collect = (n) => {
1357
+ if (n.id !== "") ids.add(n.id);
1358
+ for (const c of n.children) collect(c);
1359
+ };
1360
+ for (const root of parseMetadataForest(text).roots) collect(root);
1361
+ ownIdsByRep.set(rep.slug, ids);
1362
+ for (const id of ids) ownIds.add(id);
1363
+ } catch {
1364
+ }
1365
+ }
1366
+ entries.push({
1367
+ dir,
1368
+ displayName: manifest.component,
1369
+ ...manifest.figmaFile !== void 0 ? { figmaFile: manifest.figmaFile } : {},
1370
+ ...manifest.componentSetNode !== void 0 ? { componentSetNode: manifest.componentSetNode } : {},
1371
+ ...manifest.figmaComponentName !== void 0 ? { figmaComponentName: manifest.figmaComponentName } : {},
1372
+ variantNodeIds,
1373
+ repSlugByVariantNode,
1374
+ ownIdsByRep,
1375
+ ownIds
1376
+ });
1377
+ }
1378
+ return entries;
1379
+ }
1380
+ function sameComponent(a, b) {
1381
+ if (a.figmaFile !== void 0 && b.figmaFile !== void 0 && a.figmaFile !== b.figmaFile) return false;
1382
+ if (a.componentSetNode !== void 0 && a.componentSetNode === b.componentSetNode && a.figmaFile === b.figmaFile) return true;
1383
+ for (const id of a.variantNodeIds) if (b.variantNodeIds.has(id)) return true;
1384
+ return false;
1385
+ }
1386
+ function emissionTails(setDir, repSlug) {
1387
+ const file = path2.join(setDir, repSlug, "get_design_context.json");
1388
+ if (!existsSync2(file)) return /* @__PURE__ */ new Map();
1389
+ let text;
1390
+ try {
1391
+ text = envelopeTextContent(JSON.parse(readFileSync2(file, "utf8")));
1392
+ } catch {
1393
+ return /* @__PURE__ */ new Map();
1394
+ }
1395
+ const cut = text.search(FOOTER);
1396
+ if (cut !== -1) text = text.slice(0, cut);
1397
+ const byHead = /* @__PURE__ */ new Map();
1398
+ for (const m of text.matchAll(/data-node-id="I([^"]+)"/g)) {
1399
+ const segs = m[1].split(";");
1400
+ const head = segs[0];
1401
+ if (!byHead.has(head)) byHead.set(head, /* @__PURE__ */ new Map());
1402
+ const tails = byHead.get(head);
1403
+ segs.slice(1).forEach((tail, i) => {
1404
+ const d = i + 1;
1405
+ if (!tails.has(tail) || tails.get(tail) > d) tails.set(tail, d);
1406
+ });
1407
+ }
1408
+ return byHead;
1409
+ }
1410
+ function hostInstances(setDir, repSlug) {
1411
+ const metaFile = path2.join(setDir, repSlug, "get_metadata.json");
1412
+ if (!existsSync2(metaFile)) return [];
1413
+ try {
1414
+ const text = envelopeTextContent(JSON.parse(readFileSync2(metaFile, "utf8")));
1415
+ const out = [];
1416
+ const walk2 = (n) => {
1417
+ if (n.type === "INSTANCE") out.push({ id: n.id, name: n.name });
1418
+ for (const c of n.children) walk2(c);
1419
+ };
1420
+ for (const root of parseMetadataForest(text).roots) walk2(root);
1421
+ return out;
1422
+ } catch {
1423
+ return [];
1424
+ }
1425
+ }
1426
+ function composeReport(index) {
1427
+ const edges = [];
1428
+ for (const host of index) {
1429
+ for (const [variantNodeId, slug] of host.repSlugByVariantNode) {
1430
+ void variantNodeId;
1431
+ const instances = hostInstances(host.dir, slug);
1432
+ if (instances.length === 0) continue;
1433
+ const tailsByHead = emissionTails(host.dir, slug);
1434
+ for (const inst of instances) {
1435
+ const tails = tailsByHead.get(inst.id) ?? /* @__PURE__ */ new Map();
1436
+ const disclosures = [];
1437
+ const refused = [];
1438
+ const idCands = index.filter((c) => {
1439
+ if (c.dir === host.dir || sameComponent(c, host)) return false;
1440
+ for (const t of tails.keys()) if (c.ownIds.has(t)) return true;
1441
+ return false;
1442
+ });
1443
+ const survivors = [];
1444
+ for (const c of idCands) {
1445
+ if (host.figmaFile !== void 0 && c.figmaFile !== void 0 && c.figmaFile !== host.figmaFile) {
1446
+ disclosures.push(
1447
+ `LIBRARY-EDGE (not auto-joined): id evidence points at ${kitLabel(c)} recorded from a DIFFERENT file \u2014 a genuine cross-file/library relationship the same-file rule refuses; the future cross-file ADR owns this class`
1448
+ );
1449
+ refused.push(c);
1450
+ continue;
1451
+ }
1452
+ survivors.push(c);
1453
+ }
1454
+ const groups = [];
1455
+ for (const c of survivors) {
1456
+ const g = groups.find((grp) => grp.some((m) => sameComponent(m, c)));
1457
+ if (g) g.push(c);
1458
+ else groups.push([c]);
1459
+ }
1460
+ const eligible = [];
1461
+ for (const g of groups) {
1462
+ const uncaptured = [host.figmaFile === void 0 ? "the host set" : void 0, ...g.map((m) => m.figmaFile === void 0 ? path2.basename(m.dir) : void 0)].filter(
1463
+ (x) => x !== void 0
1464
+ );
1465
+ if (uncaptured.length > 0) {
1466
+ disclosures.push(`identity unproven: file identity is not captured on ${uncaptured.join(", ")} (recorded before slice 1) \u2014 the join rests on id evidence alone`);
1467
+ }
1468
+ eligible.push(g);
1469
+ }
1470
+ const nameProps = index.filter((c) => {
1471
+ if (c.dir === host.dir || sameComponent(c, host)) return false;
1472
+ if (refused.some((r) => r.dir === c.dir || sameComponent(r, c))) return false;
1473
+ const identityName = c.figmaComponentName ?? c.displayName;
1474
+ return norm(identityName) === norm(inst.name);
1475
+ });
1476
+ if (eligible.length === 0) {
1477
+ if (nameProps.length > 0) {
1478
+ edges.push({
1479
+ hostSet: host.dir,
1480
+ hostRep: slug,
1481
+ instanceId: inst.id,
1482
+ instanceName: inst.name,
1483
+ kind: "proposal",
1484
+ partners: nameProps.map((p) => ({ dir: p.dir, displayName: p.displayName, ...p.figmaFile !== void 0 ? { figmaFile: p.figmaFile } : {} })),
1485
+ disclosures: [
1486
+ ...disclosures,
1487
+ `NAME-ONLY proposal \u2014 identity unproven, name evidence never auto-joins: instance "${inst.name}" names ${nameProps.map(kitLabel).join(" / ")}; confirm to compose (a later slice), or ignore`
1488
+ ]
1489
+ });
1490
+ } else if (disclosures.length > 0) {
1491
+ edges.push({ hostSet: host.dir, hostRep: slug, instanceId: inst.id, instanceName: inst.name, kind: "external", partners: [], disclosures });
1492
+ }
1493
+ continue;
1494
+ }
1495
+ if (eligible.length > 1) {
1496
+ edges.push({
1497
+ hostSet: host.dir,
1498
+ hostRep: slug,
1499
+ instanceId: inst.id,
1500
+ instanceName: inst.name,
1501
+ kind: "ask",
1502
+ partners: eligible.flat().map((p) => ({ dir: p.dir, displayName: p.displayName, ...p.figmaFile !== void 0 ? { figmaFile: p.figmaFile } : {} })),
1503
+ disclosures: [...disclosures, `AMBIGUOUS: id evidence reaches ${eligible.length} distinct components \u2014 a human picks, once, recorded (never guessed)`]
1504
+ });
1505
+ continue;
1506
+ }
1507
+ const group = eligible[0];
1508
+ const ownedDepths = [];
1509
+ for (const [t, d] of tails) if (group.some((m) => m.ownIds.has(t))) ownedDepths.push(d);
1510
+ const substitution = ownedDepths.length > 0 && ownedDepths.every((d) => d === 1);
1511
+ const poseVariants = /* @__PURE__ */ new Set();
1512
+ for (const m of group) {
1513
+ for (const [t, d] of tails) {
1514
+ if (d !== 1 || !m.ownIds.has(t)) continue;
1515
+ for (const [slug2, ids] of m.ownIdsByRep) {
1516
+ if (!ids.has(t)) continue;
1517
+ const variant = [...m.repSlugByVariantNode].find(([, s]) => s === slug2)?.[0];
1518
+ if (variant !== void 0) poseVariants.add(variant);
1519
+ }
1520
+ }
1521
+ }
1522
+ for (const p of nameProps) {
1523
+ if (!group.some((m) => sameComponent(m, p))) {
1524
+ disclosures.push(`NAME DISAGREES: instance name "${inst.name}" matches ${kitLabel(p)} while id evidence joins ${kitLabel(group[0])} \u2014 if the id join looks wrong, this is the signal`);
1525
+ }
1526
+ }
1527
+ if (!substitution) {
1528
+ edges.push({
1529
+ hostSet: host.dir,
1530
+ hostRep: slug,
1531
+ instanceId: inst.id,
1532
+ instanceName: inst.name,
1533
+ kind: "nested",
1534
+ partners: group.map((p) => ({ dir: p.dir, displayName: p.displayName, ...p.figmaFile !== void 0 ? { figmaFile: p.figmaFile } : {} })),
1535
+ disclosures: [...disclosures, "GENUINE-NESTED: id evidence sits below depth 1 (through an intermediate instance) \u2014 a real relationship, never a substitution target"]
1536
+ });
1537
+ continue;
1538
+ }
1539
+ if (poseVariants.size > 1) {
1540
+ edges.push({
1541
+ hostSet: host.dir,
1542
+ hostRep: slug,
1543
+ instanceId: inst.id,
1544
+ instanceName: inst.name,
1545
+ kind: "ask",
1546
+ partners: group.map((p) => ({ dir: p.dir, displayName: p.displayName, ...p.figmaFile !== void 0 ? { figmaFile: p.figmaFile } : {} })),
1547
+ disclosures: [...disclosures, `POSE-AMBIGUOUS: depth-1 evidence spans ${poseVariants.size} distinct variants \u2014 a human picks`]
1548
+ });
1549
+ continue;
1550
+ }
1551
+ const variantNode = [...poseVariants][0];
1552
+ edges.push({
1553
+ hostSet: host.dir,
1554
+ hostRep: slug,
1555
+ instanceId: inst.id,
1556
+ instanceName: inst.name,
1557
+ kind: "substitution",
1558
+ partners: group.map((p) => ({ dir: p.dir, displayName: p.displayName, ...p.figmaFile !== void 0 ? { figmaFile: p.figmaFile } : {} })),
1559
+ pose: {
1560
+ variantNodeId: variantNode,
1561
+ reps: group.filter((m) => m.repSlugByVariantNode.has(variantNode)).map((m) => ({ dir: m.dir, slug: m.repSlugByVariantNode.get(variantNode) }))
1562
+ },
1563
+ disclosures: [
1564
+ ...disclosures,
1565
+ "substitution-grade: every owned tail at depth 1; pixel-neutrality is NOT asserted here \u2014 instance overrides are measured real, and the score-time check (a later slice) is the judge"
1566
+ ]
1567
+ });
1568
+ }
1569
+ }
1570
+ }
1571
+ return edges;
1572
+ }
1573
+ function confirmedCompositionStatus(hostSet) {
1574
+ const manifestFile = path2.join(hostSet, "recording-set.json");
1575
+ if (!existsSync2(manifestFile)) return { rows: [] };
1576
+ let rawEntries;
1577
+ try {
1578
+ const parsed = JSON.parse(readFileSync2(manifestFile, "utf8"));
1579
+ rawEntries = Array.isArray(parsed["compositions"]) ? parsed["compositions"] : [];
1580
+ } catch {
1581
+ return { rows: [], malformed: "the recording-set manifest is not readable JSON" };
1582
+ }
1583
+ const entries = [];
1584
+ const malformedEntries = [];
1585
+ for (let i = 0; i < rawEntries.length; i++) {
1586
+ const parsed = CompositionEntrySchema.safeParse(rawEntries[i]);
1587
+ if (!parsed.success) {
1588
+ malformedEntries.push(`compositions[${i}] is not a valid v1 entry (${parsed.error.issues[0]?.message ?? "invalid"})`);
1589
+ continue;
1590
+ }
1591
+ entries.push(parsed.data);
1592
+ }
1593
+ const confirmed = entries.filter((e) => e.status === "confirmed");
1594
+ const rows = [];
1595
+ for (const entry of confirmed) {
1596
+ const partnerRels = Object.keys(entry.partner.manifestSha256).map(fromStoredRel);
1597
+ const partnerDirs = partnerRels.map((rel) => path2.resolve(hostSet, rel));
1598
+ const entryKey = fromStoredRel(entry.partner.key);
1599
+ const affectedReps = [...new Set(entry.instances.map((i) => i.hostRep))];
1600
+ const remediation = `re-run \`tendril compose --set ${hostSet}\` after repairing`;
1601
+ const missing = partnerDirs.filter((d) => !existsSync2(path2.join(d, "recording-set.json")));
1602
+ if (missing.length > 0) {
1603
+ rows.push({
1604
+ key: entryKey,
1605
+ displayName: entry.partner.displayName,
1606
+ status: "partner-missing",
1607
+ instances: entry.instances,
1608
+ affectedReps,
1609
+ detail: `confirmed partner set(s) not found: ${missing.map((d) => toPosixRel(path2.relative(hostSet, d))).join(", ")} \u2014 the decision names recordings that are not there; ${remediation}`
1610
+ });
1611
+ continue;
1612
+ }
1613
+ let stale = false;
1614
+ let unreadable;
1615
+ for (const rel of partnerRels) {
1616
+ const file = path2.join(path2.resolve(hostSet, rel), "recording-set.json");
1617
+ try {
1618
+ const bytes = readFileSync2(file);
1619
+ loadManifest(path2.resolve(hostSet, rel));
1620
+ const storedSha = entry.partner.manifestSha256[Object.keys(entry.partner.manifestSha256).find((k) => fromStoredRel(k) === rel)];
1621
+ if (createHashHex(bytes) !== storedSha) stale = true;
1622
+ } catch (err) {
1623
+ unreadable = `partner ${rel} is present but not readable as a recording set (${err instanceof Error ? err.message.split("\n")[0] : String(err)})`;
1624
+ break;
1625
+ }
1626
+ }
1627
+ if (unreadable !== void 0) {
1628
+ rows.push({
1629
+ key: entryKey,
1630
+ displayName: entry.partner.displayName,
1631
+ status: "partner-unreadable",
1632
+ instances: entry.instances,
1633
+ affectedReps,
1634
+ detail: `INSTRUMENT FAILURE, not an evidence verdict: ${unreadable} \u2014 the backstop could not re-derive; ${remediation}`
1635
+ });
1636
+ continue;
1637
+ }
1638
+ const edges = composeReport(buildComposeIndex([hostSet, ...partnerDirs]));
1639
+ const supportedInstances = /* @__PURE__ */ new Map();
1640
+ for (const e of edges) {
1641
+ if (e.hostSet !== path2.resolve(hostSet) || e.kind !== "substitution" || e.pose === void 0) continue;
1642
+ if (pairKeyFor(path2.resolve(hostSet), e.partners.map((p) => p.dir)) !== entryKey) continue;
1643
+ supportedInstances.set(`${e.hostRep}\0${e.instanceId}`, e.pose.variantNodeId);
1644
+ }
1645
+ const unsupported = entry.instances.filter((i) => supportedInstances.get(`${i.hostRep}\0${i.instanceId}`) !== i.poseVariantNodeId);
1646
+ const ok = unsupported.length === 0;
1647
+ rows.push({
1648
+ key: entryKey,
1649
+ displayName: entry.partner.displayName,
1650
+ status: ok ? stale ? "stale-supported" : "supported" : stale ? "stale-unsupported" : "unsupported",
1651
+ instances: entry.instances,
1652
+ affectedReps: ok ? [] : [...new Set(unsupported.map((i) => i.hostRep))],
1653
+ detail: ok ? stale ? "the partner manifest changed since the decision (pinned bytes differ) \u2014 the CURRENT recordings still support every confirmed edge" : "the recordings support every confirmed edge (identity, rep attribution and pose re-derived)" : `${unsupported.length} confirmed instance(s) are NOT supported by the current recordings (${unsupported.map((i) => `${i.hostRep}/${i.instanceId}`).join(", ")}) \u2014 a confirmed composition the evidence does not derive; ${remediation}`
1654
+ });
1655
+ }
1656
+ return { rows, ...malformedEntries.length > 0 ? { malformed: malformedEntries.join("; ") } : {} };
1657
+ }
1658
+ function createHashHex(bytes) {
1659
+ return createHash("sha256").update(bytes).digest("hex");
1660
+ }
1661
+ var CompositionEntrySchema, norm, FOOTER, kitLabel, toPosixRel, fromStoredRel, pairKeyFor;
1662
+ var init_compose = __esm({
1663
+ "packages/figma/src/recording/compose.ts"() {
1664
+ "use strict";
1665
+ init_session();
1666
+ init_normalize();
1667
+ CompositionEntrySchema = z5.object({
1668
+ v: z5.literal(1),
1669
+ partner: z5.object({
1670
+ key: z5.string().min(1),
1671
+ displayName: z5.string(),
1672
+ figmaFile: z5.string().optional(),
1673
+ manifestSha256: z5.record(z5.string(), z5.string().regex(/^[0-9a-f]{64}$/))
1674
+ }),
1675
+ instances: z5.array(z5.object({ hostRep: z5.string(), instanceId: z5.string(), poseVariantNodeId: z5.string() })).min(1),
1676
+ status: z5.enum(["confirmed", "declined"])
1677
+ });
1678
+ norm = (s) => s.toLowerCase().replace(/\s+/g, " ").trim();
1679
+ FOOTER = /Node ids have been added to the code as data attributes/i;
1680
+ kitLabel = (e) => `${e.displayName} [${path2.basename(e.dir)}${e.figmaFile !== void 0 ? `, file ${e.figmaFile}` : ", file identity NOT captured"}]`;
1681
+ toPosixRel = (rel) => rel.split(path2.sep).join("/");
1682
+ fromStoredRel = (rel) => rel.replace(/\\/g, "/");
1683
+ pairKeyFor = (hostSet, dirs) => dirs.map((d) => toPosixRel(path2.relative(hostSet, d))).sort().join("+");
1684
+ }
1685
+ });
1686
+
1687
+ // packages/figma/src/recording/roles.ts
1688
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
1689
+ import path3 from "node:path";
1303
1690
  function emissionIdSets(emission) {
1304
1691
  const own = /* @__PURE__ */ new Set();
1305
1692
  const referenced = /* @__PURE__ */ new Set();
@@ -1318,10 +1705,10 @@ function emissionIdSets(emission) {
1318
1705
  }
1319
1706
  function deriveRoles(setDir, manifest) {
1320
1707
  const m = manifest ?? loadManifest(setDir);
1321
- const reps = m.reps.filter((r) => existsSync2(path2.join(setDir, r.slug, "get_design_context.json")));
1708
+ const reps = m.reps.filter((r) => existsSync3(path3.join(setDir, r.slug, "get_design_context.json")));
1322
1709
  const sets = /* @__PURE__ */ new Map();
1323
1710
  for (const rep of reps) {
1324
- const env = JSON.parse(readFileSync2(path2.join(setDir, rep.slug, "get_design_context.json"), "utf8"));
1711
+ const env = JSON.parse(readFileSync3(path3.join(setDir, rep.slug, "get_design_context.json"), "utf8"));
1325
1712
  const ids = emissionIdSets(env.content.map((c) => c.text ?? "").join("\n"));
1326
1713
  ids.own.add(rep.nodeId);
1327
1714
  sets.set(rep.slug, ids);
@@ -1389,6 +1776,7 @@ var init_src = __esm({
1389
1776
  init_provided_recording();
1390
1777
  init_axis_defaults();
1391
1778
  init_plan();
1779
+ init_compose();
1392
1780
  init_session();
1393
1781
  init_envelope_content();
1394
1782
  init_roles();
@@ -1399,8 +1787,8 @@ var init_src = __esm({
1399
1787
  function variableNameToPath(name) {
1400
1788
  return name.split("/").map(canonicalCssIdentPart).filter((part) => part.length > 0);
1401
1789
  }
1402
- function tokenPathToCssVar(path41) {
1403
- return `--${path41.join("-")}`;
1790
+ function tokenPathToCssVar(path44) {
1791
+ return `--${path44.join("-")}`;
1404
1792
  }
1405
1793
  function toDtcgToken(variable, defaultMode) {
1406
1794
  const modes = Object.keys(variable.valuesByMode);
@@ -1444,11 +1832,11 @@ function toDtcgToken(variable, defaultMode) {
1444
1832
  }
1445
1833
  function mapVariablesToDtcg(variables, defaultMode = "light") {
1446
1834
  const entries = variables.map((variable) => {
1447
- const path41 = variableNameToPath(variable.name);
1448
- if (path41.length === 0) {
1835
+ const path44 = variableNameToPath(variable.name);
1836
+ if (path44.length === 0) {
1449
1837
  throw new DtcgMappingError("empty-name", `Figma variable ${variable.id} has an empty name`);
1450
1838
  }
1451
- return { variable, path: path41 };
1839
+ return { variable, path: path44 };
1452
1840
  });
1453
1841
  const groupPrefixes = /* @__PURE__ */ new Set();
1454
1842
  for (const e of entries) {
@@ -1469,21 +1857,21 @@ function mapVariablesToDtcg(variables, defaultMode = "light") {
1469
1857
  }
1470
1858
  const tokens = {};
1471
1859
  const flat = [];
1472
- for (const { variable, path: path41 } of entries) {
1860
+ for (const { variable, path: path44 } of entries) {
1473
1861
  const token = toDtcgToken(variable, defaultMode);
1474
1862
  let group = tokens;
1475
- for (const segment of path41.slice(0, -1)) {
1863
+ for (const segment of path44.slice(0, -1)) {
1476
1864
  const existing = group[segment];
1477
1865
  group = existing ?? (group[segment] = {});
1478
1866
  }
1479
- const leaf = path41[path41.length - 1];
1867
+ const leaf = path44[path44.length - 1];
1480
1868
  if (group[leaf] !== void 0) {
1481
- throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path41.join(".")}" (variable ${variable.id})`);
1869
+ throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path44.join(".")}" (variable ${variable.id})`);
1482
1870
  }
1483
1871
  group[leaf] = token;
1484
1872
  flat.push({
1485
- path: path41.join("."),
1486
- cssVar: tokenPathToCssVar(path41),
1873
+ path: path44.join("."),
1874
+ cssVar: tokenPathToCssVar(path44),
1487
1875
  type: token.$type,
1488
1876
  value: token.$value
1489
1877
  });
@@ -1672,9 +2060,9 @@ function boundId(value) {
1672
2060
  return isObject(value) && typeof value["boundVariableId"] === "string" ? value["boundVariableId"] : void 0;
1673
2061
  }
1674
2062
  function resolveBinding(ctx, id) {
1675
- const path41 = ctx.pathById.get(id);
1676
- if (path41 === void 0) ctx.unresolved.add(id);
1677
- return path41;
2063
+ const path44 = ctx.pathById.get(id);
2064
+ if (path44 === void 0) ctx.unresolved.add(id);
2065
+ return path44;
1678
2066
  }
1679
2067
  function parseVariantProps(name) {
1680
2068
  if (!name.includes("=")) return void 0;
@@ -1709,8 +2097,8 @@ function walk(ctx, raw) {
1709
2097
  if (!isObject(paint) || paint["visible"] === false) continue;
1710
2098
  const id = boundId(paint);
1711
2099
  if (id !== void 0) {
1712
- const path41 = resolveBinding(ctx, id);
1713
- if (path41 !== void 0) tokens.add(path41);
2100
+ const path44 = resolveBinding(ctx, id);
2101
+ if (path44 !== void 0) tokens.add(path44);
1714
2102
  } else if (typeof paint["color"] === "string") {
1715
2103
  ctx.hardcoded.push({ node: name, property, value: paint["color"] });
1716
2104
  }
@@ -1718,8 +2106,8 @@ function walk(ctx, raw) {
1718
2106
  }
1719
2107
  const radiusId = boundId(raw["cornerRadius"]);
1720
2108
  if (radiusId !== void 0) {
1721
- const path41 = resolveBinding(ctx, radiusId);
1722
- if (path41 !== void 0) tokens.add(path41);
2109
+ const path44 = resolveBinding(ctx, radiusId);
2110
+ if (path44 !== void 0) tokens.add(path44);
1723
2111
  } else if (typeof raw["cornerRadius"] === "number" && raw["cornerRadius"] !== 0) {
1724
2112
  ctx.hardcoded.push({ node: name, property: "border-radius", value: `${raw["cornerRadius"]}px` });
1725
2113
  }
@@ -1729,10 +2117,10 @@ function walk(ctx, raw) {
1729
2117
  layout = { mode: layoutMode === "HORIZONTAL" ? "flex-row" : "flex-column" };
1730
2118
  const gapId = boundId(raw["itemSpacing"]);
1731
2119
  if (gapId !== void 0) {
1732
- const path41 = resolveBinding(ctx, gapId);
1733
- if (path41 !== void 0) {
1734
- layout.gap = path41;
1735
- tokens.add(path41);
2120
+ const path44 = resolveBinding(ctx, gapId);
2121
+ if (path44 !== void 0) {
2122
+ layout.gap = path44;
2123
+ tokens.add(path44);
1736
2124
  }
1737
2125
  } else if (typeof raw["itemSpacing"] === "number" && raw["itemSpacing"] !== 0) {
1738
2126
  ctx.hardcoded.push({ node: name, property: "gap", value: `${raw["itemSpacing"]}px` });
@@ -1741,10 +2129,10 @@ function walk(ctx, raw) {
1741
2129
  for (const field of PADDING_FIELDS) {
1742
2130
  const id = boundId(raw[field]);
1743
2131
  if (id !== void 0) {
1744
- const path41 = resolveBinding(ctx, id);
1745
- if (path41 !== void 0) {
1746
- paddingPaths.push(path41);
1747
- tokens.add(path41);
2132
+ const path44 = resolveBinding(ctx, id);
2133
+ if (path44 !== void 0) {
2134
+ paddingPaths.push(path44);
2135
+ tokens.add(path44);
1748
2136
  }
1749
2137
  } else if (typeof raw[field] === "number" && raw[field] !== 0) {
1750
2138
  ctx.hardcoded.push({ node: name, property: field, value: `${raw[field]}px` });
@@ -1952,25 +2340,25 @@ var init_src3 = __esm({
1952
2340
  });
1953
2341
 
1954
2342
  // packages/cli/src/invocation.ts
1955
- import { existsSync as existsSync3, realpathSync } from "node:fs";
1956
- import path3 from "node:path";
2343
+ import { existsSync as existsSync4, realpathSync } from "node:fs";
2344
+ import path4 from "node:path";
1957
2345
  import { fileURLToPath } from "node:url";
1958
2346
  function findPathTendril(pathEnv, platform) {
1959
- const dirs = pathEnv.split(path3.delimiter).filter((d) => d !== "" && !/node_modules[\\/]\.bin/.test(d) && !/[\\/]_npx[\\/]/.test(d));
2347
+ const dirs = pathEnv.split(path4.delimiter).filter((d) => d !== "" && !/node_modules[\\/]\.bin/.test(d) && !/[\\/]_npx[\\/]/.test(d));
1960
2348
  const names = platform === "win32" ? ["tendril.cmd", "tendril.bat"] : ["tendril"];
1961
2349
  for (const dir of dirs) {
1962
2350
  for (const name of names) {
1963
- const candidate = path3.join(dir, name);
1964
- if (existsSync3(candidate)) return candidate;
2351
+ const candidate = path4.join(dir, name);
2352
+ if (existsSync4(candidate)) return candidate;
1965
2353
  }
1966
2354
  }
1967
2355
  return null;
1968
2356
  }
1969
2357
  function packageRootOf(file) {
1970
- let dir = path3.dirname(file);
2358
+ let dir = path4.dirname(file);
1971
2359
  for (; ; ) {
1972
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
1973
- const parent = path3.dirname(dir);
2360
+ if (existsSync4(path4.join(dir, "package.json"))) return dir;
2361
+ const parent = path4.dirname(dir);
1974
2362
  if (parent === dir) return null;
1975
2363
  dir = parent;
1976
2364
  }
@@ -2009,18 +2397,18 @@ var init_invocation = __esm({
2009
2397
 
2010
2398
  // packages/verify/src/browser.ts
2011
2399
  import { execFileSync } from "node:child_process";
2012
- import { existsSync as existsSync4, readdirSync } from "node:fs";
2013
- import path4 from "node:path";
2400
+ import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
2401
+ import path5 from "node:path";
2014
2402
  function resolveChrome() {
2015
2403
  const fromEnv = process.env["TENDRIL_CHROME"] ?? process.env["CHROME_PATH"];
2016
2404
  if (fromEnv !== void 0 && fromEnv !== "") {
2017
- if (!existsSync4(fromEnv)) {
2405
+ if (!existsSync5(fromEnv)) {
2018
2406
  throw new Error(`CHROME_PATH points at ${fromEnv}, which does not exist \u2014 fix the variable or unset it to use discovery`);
2019
2407
  }
2020
2408
  return fromEnv;
2021
2409
  }
2022
2410
  const candidates = process.platform === "darwin" ? MAC_CANDIDATES : process.platform === "win32" ? WIN_CANDIDATES : [];
2023
- for (const c of candidates) if (existsSync4(c)) return c;
2411
+ for (const c of candidates) if (existsSync5(c)) return c;
2024
2412
  if (process.platform !== "win32") {
2025
2413
  for (const name of PATH_NAMES) {
2026
2414
  try {
@@ -2034,7 +2422,7 @@ function resolveChrome() {
2034
2422
  }
2035
2423
  function versionFromInstallDir(exePath) {
2036
2424
  try {
2037
- 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));
2425
+ const builds = readdirSync2(path5.dirname(exePath), { withFileTypes: true }).filter((e) => e.isDirectory() && /^\d+(\.\d+){3}$/.test(e.name)).map((e) => e.name.split(".").map(Number));
2038
2426
  builds.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2] || a[3] - b[3]);
2039
2427
  return builds.length === 0 ? null : builds[builds.length - 1].join(".");
2040
2428
  } catch {
@@ -2046,7 +2434,7 @@ function windowsBrowserName(exePath) {
2046
2434
  if (p.includes("\\google\\chrome\\")) return "Google Chrome";
2047
2435
  if (p.includes("\\microsoft\\edge\\")) return "Microsoft Edge";
2048
2436
  if (p.includes("chromium")) return "Chromium";
2049
- return path4.win32.basename(exePath, ".exe");
2437
+ return path5.win32.basename(exePath, ".exe");
2050
2438
  }
2051
2439
  function chromeVersion() {
2052
2440
  if (_version !== void 0) return _version;
@@ -2081,32 +2469,32 @@ var init_browser = __esm({
2081
2469
  });
2082
2470
 
2083
2471
  // packages/verify/src/runtime.ts
2084
- import { existsSync as existsSync5, mkdtempSync, symlinkSync } from "node:fs";
2472
+ import { existsSync as existsSync6, mkdtempSync, symlinkSync } from "node:fs";
2085
2473
  import { createRequire } from "node:module";
2086
2474
  import os from "node:os";
2087
- import path5 from "node:path";
2475
+ import path6 from "node:path";
2088
2476
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2089
2477
  function runtimePackageRoot() {
2090
2478
  const env = process.env["TENDRIL_PACKAGE_ROOT"];
2091
- if (env !== void 0 && env !== "") return path5.resolve(env);
2092
- return path5.resolve(path5.dirname(fileURLToPath2(import.meta.url)), "..");
2479
+ if (env !== void 0 && env !== "") return path6.resolve(env);
2480
+ return path6.resolve(path6.dirname(fileURLToPath2(import.meta.url)), "..");
2093
2481
  }
2094
2482
  function runtimeNodeModules() {
2095
2483
  const root = runtimePackageRoot();
2096
- for (let dir = root; ; dir = path5.dirname(dir)) {
2097
- const candidate = path5.basename(dir) === "node_modules" ? dir : path5.join(dir, "node_modules");
2098
- if (existsSync5(path5.join(candidate, "react"))) return candidate;
2099
- if (path5.dirname(dir) === dir) break;
2484
+ for (let dir = root; ; dir = path6.dirname(dir)) {
2485
+ const candidate = path6.basename(dir) === "node_modules" ? dir : path6.join(dir, "node_modules");
2486
+ if (existsSync6(path6.join(candidate, "react"))) return candidate;
2487
+ if (path6.dirname(dir) === dir) break;
2100
2488
  }
2101
2489
  throw new Error(
2102
2490
  `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.`
2103
2491
  );
2104
2492
  }
2105
2493
  function newScratchDir(prefix) {
2106
- const dir = mkdtempSync(path5.join(os.tmpdir(), `tendril-${prefix}-`));
2494
+ const dir = mkdtempSync(path6.join(os.tmpdir(), `tendril-${prefix}-`));
2107
2495
  const nodeModules = runtimeNodeModules();
2108
2496
  try {
2109
- symlinkSync(nodeModules, path5.join(dir, "node_modules"), "junction");
2497
+ symlinkSync(nodeModules, path6.join(dir, "node_modules"), "junction");
2110
2498
  } catch (err) {
2111
2499
  throw new Error(
2112
2500
  `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.`
@@ -2115,7 +2503,7 @@ function newScratchDir(prefix) {
2115
2503
  return dir;
2116
2504
  }
2117
2505
  function reactPinPlugin() {
2118
- const req = createRequire(path5.join(runtimePackageRoot(), "package.json"));
2506
+ const req = createRequire(path6.join(runtimePackageRoot(), "package.json"));
2119
2507
  return {
2120
2508
  name: "tendril-react-pin",
2121
2509
  setup(b) {
@@ -2168,9 +2556,9 @@ var init_gates = __esm({
2168
2556
 
2169
2557
  // packages/verify/src/tsc-check.ts
2170
2558
  import ts from "typescript";
2171
- import path6 from "node:path";
2559
+ import path7 from "node:path";
2172
2560
  function runTscStrict(files) {
2173
- const program = ts.createProgram(files.map((f) => path6.resolve(f)), STRICT_OPTIONS);
2561
+ const program = ts.createProgram(files.map((f) => path7.resolve(f)), STRICT_OPTIONS);
2174
2562
  const diagnostics = ts.getPreEmitDiagnostics(program);
2175
2563
  const mapped = diagnostics.map((d) => {
2176
2564
  const file = d.file?.fileName;
@@ -2331,7 +2719,7 @@ var init_token_lint = __esm({
2331
2719
 
2332
2720
  // packages/verify/src/loop.ts
2333
2721
  import { rmSync, writeFileSync as writeFileSync2 } from "node:fs";
2334
- import path7 from "node:path";
2722
+ import path8 from "node:path";
2335
2723
  async function runChecks(componentName, files, extraFiles, definedVars2) {
2336
2724
  const findings = [];
2337
2725
  for (const violation of scanForbiddenPatterns(files.tsx)) {
@@ -2346,10 +2734,10 @@ async function runChecks(componentName, files, extraFiles, definedVars2) {
2346
2734
  }
2347
2735
  const workDir = newScratchDir("loop");
2348
2736
  try {
2349
- const tsxPath = path7.join(workDir, `${componentName}.tsx`);
2737
+ const tsxPath = path8.join(workDir, `${componentName}.tsx`);
2350
2738
  writeFileSync2(tsxPath, files.tsx);
2351
2739
  for (const [name, content] of Object.entries(extraFiles ?? {})) {
2352
- writeFileSync2(path7.join(workDir, name), content);
2740
+ writeFileSync2(path8.join(workDir, name), content);
2353
2741
  }
2354
2742
  const tsc = runTscStrict([tsxPath]);
2355
2743
  for (const d of tsc.diagnostics) {
@@ -2414,7 +2802,7 @@ var init_loop = __esm({
2414
2802
  });
2415
2803
 
2416
2804
  // packages/verify/src/report.ts
2417
- import { z as z5 } from "zod";
2805
+ import { z as z6 } from "zod";
2418
2806
  function isTokenEnforcedProperty(property) {
2419
2807
  return enforcedMatchers.some((m) => m.test(property));
2420
2808
  }
@@ -2456,58 +2844,58 @@ var init_report = __esm({
2456
2844
  "packages/verify/src/report.ts"() {
2457
2845
  "use strict";
2458
2846
  init_token_lint();
2459
- ReportSchema = z5.object({
2460
- component: z5.string(),
2461
- tokenAdherence: z5.number().min(0).max(100),
2462
- tokenViolations: z5.number(),
2463
- tscPass: z5.boolean(),
2464
- gatesClean: z5.boolean(),
2847
+ ReportSchema = z6.object({
2848
+ component: z6.string(),
2849
+ tokenAdherence: z6.number().min(0).max(100),
2850
+ tokenViolations: z6.number(),
2851
+ tscPass: z6.boolean(),
2852
+ gatesClean: z6.boolean(),
2465
2853
  /** null = check skipped in this phase (recorded in skippedChecks). */
2466
- axeViolations: z5.number().nullable(),
2467
- visualDiff: z5.object({
2468
- method: z5.string(),
2469
- score: z5.number().nullable(),
2470
- flags: z5.array(z5.string()),
2854
+ axeViolations: z6.number().nullable(),
2855
+ visualDiff: z6.object({
2856
+ method: z6.string(),
2857
+ score: z6.number().nullable(),
2858
+ flags: z6.array(z6.string()),
2471
2859
  /** Reference-image oracle (ADR-004 pillar B). */
2472
- imageSimilarity: z5.object({
2473
- variants: z5.number(),
2474
- mean: z5.number(),
2475
- worst: z5.number(),
2476
- perVariant: z5.array(
2477
- z5.object({
2478
- label: z5.string(),
2479
- similarity: z5.number(),
2480
- ink: z5.number().optional(),
2481
- region: z5.string().optional()
2860
+ imageSimilarity: z6.object({
2861
+ variants: z6.number(),
2862
+ mean: z6.number(),
2863
+ worst: z6.number(),
2864
+ perVariant: z6.array(
2865
+ z6.object({
2866
+ label: z6.string(),
2867
+ similarity: z6.number(),
2868
+ ink: z6.number().optional(),
2869
+ region: z6.string().optional()
2482
2870
  })
2483
2871
  ).optional()
2484
2872
  }).optional(),
2485
2873
  /** What the score covers — the number carries its own caveats. */
2486
- coverage: z5.object({
2487
- factChecks: z5.number(),
2488
- imageVariants: z5.number(),
2489
- unmeasured: z5.array(z5.string())
2874
+ coverage: z6.object({
2875
+ factChecks: z6.number(),
2876
+ imageVariants: z6.number(),
2877
+ unmeasured: z6.array(z6.string())
2490
2878
  }).optional()
2491
2879
  }),
2492
- repairIterations: z5.number(),
2493
- skippedChecks: z5.array(z5.string()),
2494
- model: z5.object({ id: z5.string(), status: z5.enum(["verified", "degraded"]) }),
2880
+ repairIterations: z6.number(),
2881
+ skippedChecks: z6.array(z6.string()),
2882
+ model: z6.object({ id: z6.string(), status: z6.enum(["verified", "degraded"]) }),
2495
2883
  /** Who authored the stylesheet — keeps eval numbers comparable if
2496
2884
  * authorship ever changes (ADR-005; review finding on ADR-004). */
2497
- styling: z5.enum(["model", "compiled"]).default("model"),
2885
+ styling: z6.enum(["model", "compiled"]).default("model"),
2498
2886
  /** Per-run model usage: BYOK economics must be observable, not folklore.
2499
2887
  * Absent when the transport has no metering (mock runs). */
2500
- usage: z5.object({
2501
- calls: z5.number(),
2502
- promptTokens: z5.number(),
2503
- completionTokens: z5.number(),
2888
+ usage: z6.object({
2889
+ calls: z6.number(),
2890
+ promptTokens: z6.number(),
2891
+ completionTokens: z6.number(),
2504
2892
  /** OpenRouter-reported USD (0 when the provider didn't report). */
2505
- cost: z5.number()
2893
+ cost: z6.number()
2506
2894
  }).optional(),
2507
- transport: z5.string(),
2508
- toolVersion: z5.string(),
2509
- generatedAt: z5.string(),
2510
- needsHumanReview: z5.array(z5.string())
2895
+ transport: z6.string(),
2896
+ toolVersion: z6.string(),
2897
+ generatedAt: z6.string(),
2898
+ needsHumanReview: z6.array(z6.string())
2511
2899
  });
2512
2900
  enforcedMatchers = TOKEN_ENFORCED_PROPERTIES.map(
2513
2901
  (entry) => entry.startsWith("/") && entry.endsWith("/") ? new RegExp(entry.slice(1, -1)) : new RegExp(`^${entry}$`)
@@ -2991,7 +3379,7 @@ var init_image_diff = __esm({
2991
3379
  });
2992
3380
 
2993
3381
  // packages/verify/src/visual-facts.ts
2994
- import path8 from "node:path";
3382
+ import path9 from "node:path";
2995
3383
  import { fileURLToPath as fileURLToPath3 } from "node:url";
2996
3384
  import { build } from "esbuild";
2997
3385
  import { chromium } from "playwright-core";
@@ -3536,7 +3924,7 @@ var init_visual_facts = __esm({
3536
3924
  init_browser();
3537
3925
  init_mount_limits();
3538
3926
  init_image_diff();
3539
- RESOLVE_DIR = path8.resolve(path8.dirname(fileURLToPath3(import.meta.url)), "..");
3927
+ RESOLVE_DIR = path9.resolve(path9.dirname(fileURLToPath3(import.meta.url)), "..");
3540
3928
  TOLERANCE_PX = 2;
3541
3929
  WIDTH_SLACK = 0.25;
3542
3930
  IMAGE_SIMILARITY_FLOOR = 0.8;
@@ -3545,14 +3933,14 @@ var init_visual_facts = __esm({
3545
3933
  });
3546
3934
 
3547
3935
  // packages/verify/src/paths.ts
3548
- import path9 from "node:path";
3936
+ import path10 from "node:path";
3549
3937
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3550
3938
  var VERIFY_PKG_DIR, REPO_ROOT;
3551
3939
  var init_paths = __esm({
3552
3940
  "packages/verify/src/paths.ts"() {
3553
3941
  "use strict";
3554
- VERIFY_PKG_DIR = path9.resolve(path9.dirname(fileURLToPath4(import.meta.url)), "..");
3555
- REPO_ROOT = path9.resolve(VERIFY_PKG_DIR, "..", "..");
3942
+ VERIFY_PKG_DIR = path10.resolve(path10.dirname(fileURLToPath4(import.meta.url)), "..");
3943
+ REPO_ROOT = path10.resolve(VERIFY_PKG_DIR, "..", "..");
3556
3944
  }
3557
3945
  });
3558
3946
 
@@ -3671,9 +4059,9 @@ var init_font_collection = __esm({
3671
4059
  });
3672
4060
 
3673
4061
  // packages/verify/src/font-discovery.ts
3674
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
4062
+ import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync4, realpathSync as realpathSync2 } from "node:fs";
3675
4063
  import os2 from "node:os";
3676
- import path10 from "node:path";
4064
+ import path11 from "node:path";
3677
4065
  function weightFromSubfamily(subfamily) {
3678
4066
  for (const [re, w] of WEIGHT_TOKENS) if (re.test(subfamily)) return w;
3679
4067
  return void 0;
@@ -3719,7 +4107,7 @@ function faceAt(bytes, view, dirOffset, file, faceIndex) {
3719
4107
  }
3720
4108
  function facesInFile(file) {
3721
4109
  try {
3722
- const bytes = new Uint8Array(readFileSync3(file));
4110
+ const bytes = new Uint8Array(readFileSync4(file));
3723
4111
  if (bytes.length < 12) return [];
3724
4112
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
3725
4113
  if (isCollection(bytes)) {
@@ -3739,13 +4127,13 @@ function facesInFile(file) {
3739
4127
  }
3740
4128
  function systemFontDirs() {
3741
4129
  const env = process.env["TENDRIL_SYSTEM_FONT_DIRS"];
3742
- if (env !== void 0 && env !== "") return env.split(path10.delimiter).filter((d) => d !== "" && existsSync6(d));
4130
+ if (env !== void 0 && env !== "") return env.split(path11.delimiter).filter((d) => d !== "" && existsSync7(d));
3743
4131
  const home = os2.homedir();
3744
- const dirs = process.platform === "darwin" ? ["/System/Library/Fonts", "/Library/Fonts", path10.join(home, "Library", "Fonts")] : process.platform === "win32" ? [
3745
- path10.join(process.env["WINDIR"] ?? "C:\\Windows", "Fonts"),
3746
- ...process.env["LOCALAPPDATA"] !== void 0 ? [path10.join(process.env["LOCALAPPDATA"], "Microsoft", "Windows", "Fonts")] : []
3747
- ] : ["/usr/share/fonts", "/usr/local/share/fonts", path10.join(home, ".fonts"), path10.join(home, ".local", "share", "fonts")];
3748
- return dirs.filter((d) => existsSync6(d));
4132
+ const dirs = process.platform === "darwin" ? ["/System/Library/Fonts", "/Library/Fonts", path11.join(home, "Library", "Fonts")] : process.platform === "win32" ? [
4133
+ path11.join(process.env["WINDIR"] ?? "C:\\Windows", "Fonts"),
4134
+ ...process.env["LOCALAPPDATA"] !== void 0 ? [path11.join(process.env["LOCALAPPDATA"], "Microsoft", "Windows", "Fonts")] : []
4135
+ ] : ["/usr/share/fonts", "/usr/local/share/fonts", path11.join(home, ".fonts"), path11.join(home, ".local", "share", "fonts")];
4136
+ return dirs.filter((d) => existsSync7(d));
3749
4137
  }
3750
4138
  function discoverSystemFaces(dirs = systemFontDirs(), depth = 3) {
3751
4139
  const seen = /* @__PURE__ */ new Set();
@@ -3754,15 +4142,15 @@ function discoverSystemFaces(dirs = systemFontDirs(), depth = 3) {
3754
4142
  for (const dir of dirList) {
3755
4143
  let entries;
3756
4144
  try {
3757
- entries = readdirSync2(dir, { withFileTypes: true });
4145
+ entries = readdirSync3(dir, { withFileTypes: true });
3758
4146
  } catch {
3759
4147
  continue;
3760
4148
  }
3761
4149
  for (const e of entries) {
3762
- const full = path10.join(dir, e.name);
4150
+ const full = path11.join(dir, e.name);
3763
4151
  if (e.isDirectory()) {
3764
4152
  if (remaining > 1) faces.push(...walk2([full], remaining - 1));
3765
- } else if (FONT_EXTENSIONS.has(path10.extname(e.name).toLowerCase())) {
4153
+ } else if (FONT_EXTENSIONS.has(path11.extname(e.name).toLowerCase())) {
3766
4154
  let key = full;
3767
4155
  try {
3768
4156
  key = realpathSync2(full);
@@ -3810,14 +4198,14 @@ var init_font_discovery = __esm({
3810
4198
  });
3811
4199
 
3812
4200
  // packages/verify/src/font-resolve.ts
3813
- import { createHash } from "node:crypto";
3814
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
4201
+ import { createHash as createHash2 } from "node:crypto";
4202
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
3815
4203
  import os3 from "node:os";
3816
- import path11 from "node:path";
4204
+ import path12 from "node:path";
3817
4205
  function fontCacheDir() {
3818
4206
  const env = process.env["TENDRIL_FONT_CACHE"];
3819
- if (env !== void 0 && env !== "") return path11.resolve(env);
3820
- return path11.join(os3.homedir(), ".tendril", "fonts");
4207
+ if (env !== void 0 && env !== "") return path12.resolve(env);
4208
+ return path12.join(os3.homedir(), ".tendril", "fonts");
3821
4209
  }
3822
4210
  function normalizeFontLicense(value) {
3823
4211
  return typeof value === "string" && FONT_LICENSES.includes(value) ? value : "unknown";
@@ -3881,30 +4269,30 @@ async function resolveFonts(family, weights, cacheDir = DEFAULT_FONT_CACHE) {
3881
4269
  continue;
3882
4270
  }
3883
4271
  const bytes = new Uint8Array(await fileRes.arrayBuffer());
3884
- const sha256 = createHash("sha256").update(bytes).digest("hex");
3885
- const file = path11.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}.woff2`);
4272
+ const sha256 = createHash2("sha256").update(bytes).digest("hex");
4273
+ const file = path12.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}.woff2`);
3886
4274
  writeFileSync3(file, bytes);
3887
4275
  resolved.push({ family, weight, source: url, sha256, file, license: meta.license, ...meta.family !== null ? { servedFamily: meta.family } : {} });
3888
4276
  } catch (err) {
3889
4277
  failures.push({ family, weight, reason: `download failed: ${err instanceof Error ? err.message : String(err)}` });
3890
4278
  }
3891
4279
  }
3892
- const mPath = path11.join(cacheDir, "manifest.json");
3893
- const prior = existsSync7(mPath) ? JSON.parse(readFileSync4(mPath, "utf8")) : [];
3894
- const portable2 = resolved.map((m) => ({ ...m, file: path11.basename(m.file) }));
4280
+ const mPath = path12.join(cacheDir, "manifest.json");
4281
+ const prior = existsSync8(mPath) ? JSON.parse(readFileSync5(mPath, "utf8")) : [];
4282
+ const portable2 = resolved.map((m) => ({ ...m, file: path12.basename(m.file) }));
3895
4283
  const merged = [...prior.filter((p) => !resolved.some((m) => m.family === p.family && m.weight === p.weight)), ...portable2];
3896
4284
  if (resolved.length > 0) writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
3897
4285
  `);
3898
4286
  return { resolved, failures };
3899
4287
  }
3900
4288
  function addLocalFont(family, weight, filePath, cacheDir = DEFAULT_FONT_CACHE, faceIndex, provenance = "local") {
3901
- const src = path11.resolve(filePath);
3902
- if (!existsSync7(src)) throw new Error(`font file not found: ${src}`);
3903
- const ext = path11.extname(src).toLowerCase();
4289
+ const src = path12.resolve(filePath);
4290
+ if (!existsSync8(src)) throw new Error(`font file not found: ${src}`);
4291
+ const ext = path12.extname(src).toLowerCase();
3904
4292
  if (![".woff2", ".woff", ".ttf", ".otf", ".ttc"].includes(ext)) {
3905
4293
  throw new Error(`unsupported font file "${ext}" \u2014 use .woff2 (preferred), .woff, .ttf, .otf or .ttc`);
3906
4294
  }
3907
- let bytes = new Uint8Array(readFileSync4(src));
4295
+ let bytes = new Uint8Array(readFileSync5(src));
3908
4296
  if (bytes.length === 0) throw new Error(`font file is empty: ${src}`);
3909
4297
  let storedExt = ext;
3910
4298
  if (isCollection(bytes)) {
@@ -3914,7 +4302,7 @@ function addLocalFont(family, weight, filePath, cacheDir = DEFAULT_FONT_CACHE, f
3914
4302
  const all = listCollectionFaces(bytes);
3915
4303
  const shown = (candidates.length > 0 ? candidates : all).map((f) => ` --face ${f.index} ${f.family ?? "(unnamed)"}${f.subfamily !== void 0 ? ` ${f.subfamily}` : ""}`).join("\n");
3916
4304
  throw new Error(
3917
- `${path11.basename(src)} is a collection of ${all.length} faces and ${candidates.length === 0 ? `none is named "${family}"` : `${candidates.length} match "${family}"`} \u2014 name the one you mean with --face <index>:
4305
+ `${path12.basename(src)} is a collection of ${all.length} faces and ${candidates.length === 0 ? `none is named "${family}"` : `${candidates.length} match "${family}"`} \u2014 name the one you mean with --face <index>:
3918
4306
  ${shown}`
3919
4307
  );
3920
4308
  }
@@ -3922,21 +4310,21 @@ ${shown}`
3922
4310
  storedExt = ".ttf";
3923
4311
  }
3924
4312
  mkdirSync2(cacheDir, { recursive: true });
3925
- const sha256 = createHash("sha256").update(bytes).digest("hex");
3926
- const file = path11.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}${storedExt}`);
4313
+ const sha256 = createHash2("sha256").update(bytes).digest("hex");
4314
+ const file = path12.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}${storedExt}`);
3927
4315
  writeFileSync3(file, bytes);
3928
- const face = { family, weight, source: `${provenance}:${path11.basename(src)}`, sha256, file, license: "unknown" };
3929
- const mPath = path11.join(cacheDir, "manifest.json");
3930
- const prior = existsSync7(mPath) ? JSON.parse(readFileSync4(mPath, "utf8")) : [];
3931
- const merged = [...prior.filter((p) => !(p.family === family && p.weight === weight)), { ...face, file: path11.basename(file) }];
4316
+ const face = { family, weight, source: `${provenance}:${path12.basename(src)}`, sha256, file, license: "unknown" };
4317
+ const mPath = path12.join(cacheDir, "manifest.json");
4318
+ const prior = existsSync8(mPath) ? JSON.parse(readFileSync5(mPath, "utf8")) : [];
4319
+ const merged = [...prior.filter((p) => !(p.family === family && p.weight === weight)), { ...face, file: path12.basename(file) }];
3932
4320
  writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
3933
4321
  `);
3934
4322
  return face;
3935
4323
  }
3936
4324
  function checkFontLock(lockPath, cacheDir = DEFAULT_FONT_CACHE) {
3937
- const lock = JSON.parse(readFileSync4(lockPath, "utf8"));
3938
- const mPath = path11.join(cacheDir, "manifest.json");
3939
- const manifest = existsSync7(mPath) ? JSON.parse(readFileSync4(mPath, "utf8")) : [];
4325
+ const lock = JSON.parse(readFileSync5(lockPath, "utf8"));
4326
+ const mPath = path12.join(cacheDir, "manifest.json");
4327
+ const manifest = existsSync8(mPath) ? JSON.parse(readFileSync5(mPath, "utf8")) : [];
3940
4328
  return lock.map((l) => {
3941
4329
  const m = manifest.find((x) => x.family === l.family && x.weight === l.weight);
3942
4330
  if (m === void 0) return { family: l.family, weight: l.weight, status: "missing" };
@@ -3944,11 +4332,11 @@ function checkFontLock(lockPath, cacheDir = DEFAULT_FONT_CACHE) {
3944
4332
  });
3945
4333
  }
3946
4334
  function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
3947
- const mPath = path11.join(cacheDir, "manifest.json");
3948
- if (!existsSync7(mPath)) return [];
4335
+ const mPath = path12.join(cacheDir, "manifest.json");
4336
+ if (!existsSync8(mPath)) return [];
3949
4337
  let entries;
3950
4338
  try {
3951
- entries = JSON.parse(readFileSync4(mPath, "utf8"));
4339
+ entries = JSON.parse(readFileSync5(mPath, "utf8"));
3952
4340
  } catch {
3953
4341
  return [];
3954
4342
  }
@@ -3962,8 +4350,8 @@ function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
3962
4350
  return [...byFamily.entries()].map(([family, w]) => ({ family, weights: [...w].sort((a, b) => a - b) }));
3963
4351
  }
3964
4352
  function requiredFontsManifest(families, cacheDir = DEFAULT_FONT_CACHE) {
3965
- const mPath = path11.join(cacheDir, "manifest.json");
3966
- const manifest = existsSync7(mPath) ? JSON.parse(readFileSync4(mPath, "utf8")) : [];
4353
+ const mPath = path12.join(cacheDir, "manifest.json");
4354
+ const manifest = existsSync8(mPath) ? JSON.parse(readFileSync5(mPath, "utf8")) : [];
3967
4355
  const wanted = new Set(families.map((f) => f.toLowerCase()));
3968
4356
  return manifest.filter((f) => wanted.has(f.family.toLowerCase())).map((f) => ({
3969
4357
  family: f.family,
@@ -3976,11 +4364,11 @@ function requiredFontsManifest(families, cacheDir = DEFAULT_FONT_CACHE) {
3976
4364
  }));
3977
4365
  }
3978
4366
  function facesMissingLicense(cacheDir = DEFAULT_FONT_CACHE) {
3979
- const mPath = path11.join(cacheDir, "manifest.json");
3980
- if (!existsSync7(mPath)) return [];
4367
+ const mPath = path12.join(cacheDir, "manifest.json");
4368
+ if (!existsSync8(mPath)) return [];
3981
4369
  let entries;
3982
4370
  try {
3983
- entries = JSON.parse(readFileSync4(mPath, "utf8"));
4371
+ entries = JSON.parse(readFileSync5(mPath, "utf8"));
3984
4372
  } catch {
3985
4373
  return [];
3986
4374
  }
@@ -3989,20 +4377,20 @@ function facesMissingLicense(cacheDir = DEFAULT_FONT_CACHE) {
3989
4377
  ).map((e) => ({ family: e.family, weight: e.weight, sha256: e.sha256 }));
3990
4378
  }
3991
4379
  function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
3992
- const mPath = path11.join(cacheDir, "manifest.json");
3993
- if (!existsSync7(mPath)) return [];
4380
+ const mPath = path12.join(cacheDir, "manifest.json");
4381
+ if (!existsSync8(mPath)) return [];
3994
4382
  let entries;
3995
4383
  try {
3996
- entries = JSON.parse(readFileSync4(mPath, "utf8"));
4384
+ entries = JSON.parse(readFileSync5(mPath, "utf8"));
3997
4385
  } catch {
3998
4386
  return [];
3999
4387
  }
4000
4388
  const byFamily = /* @__PURE__ */ new Map();
4001
4389
  for (const e of entries) {
4002
4390
  if (typeof e.family !== "string" || typeof e.weight !== "number" || typeof e.file !== "string") continue;
4003
- const file = path11.isAbsolute(e.file) && existsSync7(e.file) ? e.file : path11.resolve(cacheDir, path11.basename(e.file));
4004
- if (!existsSync7(file)) continue;
4005
- if (createHash("sha256").update(readFileSync4(file)).digest("hex") !== e.sha256) continue;
4391
+ const file = path12.isAbsolute(e.file) && existsSync8(e.file) ? e.file : path12.resolve(cacheDir, path12.basename(e.file));
4392
+ if (!existsSync8(file)) continue;
4393
+ if (createHash2("sha256").update(readFileSync5(file)).digest("hex") !== e.sha256) continue;
4006
4394
  const set = byFamily.get(e.family) ?? /* @__PURE__ */ new Set();
4007
4395
  set.add(e.weight);
4008
4396
  byFamily.set(e.family, set);
@@ -4023,8 +4411,8 @@ function addSystemFamily(family, opts = {}) {
4023
4411
  const skipped = [];
4024
4412
  const overwrote = [];
4025
4413
  const cacheDir = opts.cacheDir ?? DEFAULT_FONT_CACHE;
4026
- const manifestFile = path11.join(cacheDir, "manifest.json");
4027
- const prior = existsSync7(manifestFile) ? JSON.parse(readFileSync4(manifestFile, "utf8")) : [];
4414
+ const manifestFile = path12.join(cacheDir, "manifest.json");
4415
+ const prior = existsSync8(manifestFile) ? JSON.parse(readFileSync5(manifestFile, "utf8")) : [];
4028
4416
  const taken = /* @__PURE__ */ new Set();
4029
4417
  for (const face of faces) {
4030
4418
  const skip = (reason) => skipped.push({ subfamily: face.subfamily, weight: face.weight, reason });
@@ -4085,18 +4473,18 @@ var init_font_resolve = __esm({
4085
4473
  });
4086
4474
 
4087
4475
  // packages/verify/src/font-faces.ts
4088
- import { createHash as createHash2 } from "node:crypto";
4089
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "node:fs";
4090
- import path12 from "node:path";
4476
+ import { createHash as createHash3 } from "node:crypto";
4477
+ import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
4478
+ import path13 from "node:path";
4091
4479
  function injectedGroups(manifestPath2) {
4092
- if (!existsSync8(manifestPath2)) return { groups: [], shared: false };
4093
- const claimed = JSON.parse(readFileSync5(manifestPath2, "utf8"));
4094
- const resolveFile = (f) => path12.isAbsolute(f) && existsSync8(f) ? f : path12.resolve(path12.dirname(manifestPath2), path12.basename(f));
4480
+ if (!existsSync9(manifestPath2)) return { groups: [], shared: false };
4481
+ const claimed = JSON.parse(readFileSync6(manifestPath2, "utf8"));
4482
+ const resolveFile = (f) => path13.isAbsolute(f) && existsSync9(f) ? f : path13.resolve(path13.dirname(manifestPath2), path13.basename(f));
4095
4483
  const byFile = /* @__PURE__ */ new Map();
4096
4484
  for (const f of claimed) {
4097
4485
  const file = resolveFile(f.file);
4098
- if (!existsSync8(file)) continue;
4099
- if (createHash2("sha256").update(readFileSync5(file)).digest("hex") !== f.sha256) continue;
4486
+ if (!existsSync9(file)) continue;
4487
+ if (createHash3("sha256").update(readFileSync6(file)).digest("hex") !== f.sha256) continue;
4100
4488
  const k = `${f.family}:${f.file}`;
4101
4489
  const e = byFile.get(k) ?? { family: f.family, weights: [], file };
4102
4490
  e.weights.push(f.weight);
@@ -4105,14 +4493,14 @@ function injectedGroups(manifestPath2) {
4105
4493
  const groups = [...byFile.values()];
4106
4494
  return { groups, shared: new Set(groups.map((e) => e.file)).size < groups.length };
4107
4495
  }
4108
- function fontFaceCss(manifestPath2 = path12.join(fontCacheDir(), "manifest.json")) {
4496
+ function fontFaceCss(manifestPath2 = path13.join(fontCacheDir(), "manifest.json")) {
4109
4497
  const { groups, shared } = injectedGroups(manifestPath2);
4110
4498
  return groups.map((e) => {
4111
4499
  const weight = shared || e.weights.length > 1 ? `${SPAN[0]} ${SPAN[1]}` : String(e.weights[0]);
4112
- return `@font-face { font-family: '${e.family}'; font-weight: ${weight}; font-style: normal; src: url(data:font/woff2;base64,${readFileSync5(e.file).toString("base64")}) format('woff2'); }`;
4500
+ return `@font-face { font-family: '${e.family}'; font-weight: ${weight}; font-style: normal; src: url(data:font/woff2;base64,${readFileSync6(e.file).toString("base64")}) format('woff2'); }`;
4113
4501
  }).join("\n");
4114
4502
  }
4115
- function injectedFamilyWeights(manifestPath2 = path12.join(fontCacheDir(), "manifest.json")) {
4503
+ function injectedFamilyWeights(manifestPath2 = path13.join(fontCacheDir(), "manifest.json")) {
4116
4504
  const { groups, shared } = injectedGroups(manifestPath2);
4117
4505
  const out = /* @__PURE__ */ new Map();
4118
4506
  for (const g of groups) {
@@ -4137,17 +4525,17 @@ var init_font_faces = __esm({
4137
4525
  });
4138
4526
 
4139
4527
  // packages/verify/src/admission.ts
4140
- import { readFileSync as readFileSync6, readdirSync as readdirSync3, existsSync as existsSync9, writeFileSync as writeFileSync4 } from "node:fs";
4141
- import path13 from "node:path";
4528
+ import { readFileSync as readFileSync7, readdirSync as readdirSync4, existsSync as existsSync10, writeFileSync as writeFileSync4 } from "node:fs";
4529
+ import path14 from "node:path";
4142
4530
  import { build as build2 } from "esbuild";
4143
4531
  import postcss from "postcss";
4144
4532
  import tailwindcss from "tailwindcss";
4145
4533
  import { chromium as chromium2 } from "playwright-core";
4146
4534
  function fontWeightsByFamily() {
4147
- const mPath = path13.join(fontCacheDir(), "manifest.json");
4535
+ const mPath = path14.join(fontCacheDir(), "manifest.json");
4148
4536
  const out = /* @__PURE__ */ new Map();
4149
- if (!existsSync9(mPath)) return out;
4150
- for (const f of JSON.parse(readFileSync6(mPath, "utf8")))
4537
+ if (!existsSync10(mPath)) return out;
4538
+ for (const f of JSON.parse(readFileSync7(mPath, "utf8")))
4151
4539
  out.set(f.family, [...out.get(f.family) ?? [], f.weight]);
4152
4540
  return out;
4153
4541
  }
@@ -4166,8 +4554,36 @@ var init_admission = __esm({
4166
4554
  }
4167
4555
  });
4168
4556
 
4557
+ // packages/verify/src/candidate-css.ts
4558
+ import { existsSync as existsSync11, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
4559
+ import path15 from "node:path";
4560
+ function candidateCss(bundleDir) {
4561
+ const files = ["tokens.css", "styles.css"].map((f) => path15.join(bundleDir, f));
4562
+ const composedRoot = path15.join(bundleDir, "composed");
4563
+ let composedDirs = [];
4564
+ try {
4565
+ composedDirs = readdirSync5(composedRoot).sort();
4566
+ } catch {
4567
+ }
4568
+ for (const entry of composedDirs) {
4569
+ const dir = path15.join(composedRoot, entry);
4570
+ try {
4571
+ if (!statSync2(dir).isDirectory()) continue;
4572
+ } catch {
4573
+ continue;
4574
+ }
4575
+ files.push(path15.join(dir, "tokens.css"), path15.join(dir, "styles.css"));
4576
+ }
4577
+ return files.filter((f) => existsSync11(f)).map((f) => readFileSync8(f, "utf8")).join("\n");
4578
+ }
4579
+ var init_candidate_css = __esm({
4580
+ "packages/verify/src/candidate-css.ts"() {
4581
+ "use strict";
4582
+ }
4583
+ });
4584
+
4169
4585
  // packages/verify/src/tasks.ts
4170
- import path14 from "node:path";
4586
+ import path16 from "node:path";
4171
4587
  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;
4172
4588
  var init_tasks = __esm({
4173
4589
  "packages/verify/src/tasks.ts"() {
@@ -4327,7 +4743,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
4327
4743
  ];
4328
4744
  TASKS = {
4329
4745
  calendar: {
4330
- set: path14.join(REPO_ROOT, "examples/recordings/shadcn-poc-calendar"),
4746
+ set: path16.join(REPO_ROOT, "examples/recordings/shadcn-poc-calendar"),
4331
4747
  entry: "Calendar.tsx",
4332
4748
  configs: CALENDAR_CONFIGS,
4333
4749
  systemApi: CALENDAR_API,
@@ -4335,7 +4751,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
4335
4751
  prelude: { controls: ['[data-tendril-part="day"]'], textInputs: [] }
4336
4752
  },
4337
4753
  "shadcn-button": {
4338
- set: path14.join(REPO_ROOT, "examples/recordings/shadcn-poc-button"),
4754
+ set: path16.join(REPO_ROOT, "examples/recordings/shadcn-poc-button"),
4339
4755
  entry: "Button.tsx",
4340
4756
  configs: BUTTON_CONFIGS,
4341
4757
  systemApi: BUTTON_API,
@@ -4343,7 +4759,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
4343
4759
  prelude: { controls: ["> *"], textInputs: [] }
4344
4760
  },
4345
4761
  combobox: {
4346
- set: path14.join(REPO_ROOT, "examples/recordings/carbon-poc-combobox"),
4762
+ set: path16.join(REPO_ROOT, "examples/recordings/carbon-poc-combobox"),
4347
4763
  entry: "ComboBox.tsx",
4348
4764
  configs: COMBO_CONFIGS,
4349
4765
  systemApi: COMBO_API,
@@ -4351,7 +4767,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
4351
4767
  prelude: { controls: ['[role="option"]'], textInputs: ["input"], popover: { selector: '[role="listbox"]', trigger: "input" } }
4352
4768
  },
4353
4769
  modal: {
4354
- set: path14.join(REPO_ROOT, "examples/recordings/carbon-poc-modal"),
4770
+ set: path16.join(REPO_ROOT, "examples/recordings/carbon-poc-modal"),
4355
4771
  entry: "Modal.tsx",
4356
4772
  configs: MODAL_CONFIGS,
4357
4773
  systemApi: MODAL_API,
@@ -4369,8 +4785,8 @@ __export(behavior_exports, {
4369
4785
  compileMount: () => compileMount,
4370
4786
  recordingIsDark: () => recordingIsDark
4371
4787
  });
4372
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
4373
- import path15 from "node:path";
4788
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
4789
+ import path17 from "node:path";
4374
4790
  import { build as build3 } from "esbuild";
4375
4791
  import { chromium as chromium3 } from "playwright-core";
4376
4792
  import { PNG as PNG2 } from "pngjs";
@@ -4379,12 +4795,12 @@ function getFontFaces() {
4379
4795
  return _fontFaces;
4380
4796
  }
4381
4797
  async function compileMount(task, bundleDir) {
4382
- const entryTsx = path15.join(bundleDir, task.entry);
4383
- if (!existsSync10(entryTsx)) return { error: `${task.entry} missing` };
4798
+ const entryTsx = path17.join(bundleDir, task.entry);
4799
+ if (!existsSync12(entryTsx)) return { error: `${task.entry} missing` };
4384
4800
  const mountSrc = `
4385
4801
  import { createElement } from "react";
4386
4802
  import { createRoot } from "react-dom/client";
4387
- import * as B from ${JSON.stringify(path15.resolve(entryTsx))};
4803
+ import * as B from ${JSON.stringify(path17.resolve(entryTsx))};
4388
4804
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; spyProps?: string[] } }).__cfg;
4389
4805
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
4390
4806
  // Callbacks cannot ride the JSON config: specs NAME spy props and the
@@ -4665,10 +5081,10 @@ async function runSteps(page, spec, renderPose) {
4665
5081
  function recordingIsDark(task) {
4666
5082
  const rep = task.configs[0]?.rep;
4667
5083
  if (rep === void 0) return false;
4668
- const f = path15.join(task.set, rep, "get_screenshot.json");
4669
- if (!existsSync10(f)) return false;
5084
+ const f = path17.join(task.set, rep, "get_screenshot.json");
5085
+ if (!existsSync12(f)) return false;
4670
5086
  try {
4671
- const env = JSON.parse(readFileSync7(f, "utf8")).content.find((c) => c.type === "image");
5087
+ const env = JSON.parse(readFileSync9(f, "utf8")).content.find((c) => c.type === "image");
4672
5088
  if (env?.data === void 0) return false;
4673
5089
  const png = PNG2.sync.read(Buffer.from(env.data, "base64"));
4674
5090
  let sum = 0;
@@ -4740,7 +5156,7 @@ async function checkBehaviors(task, bundleDir, opts = {}) {
4740
5156
  const deadlineMs = timeoutMs + 1e4;
4741
5157
  const js = await compileMount(task, bundleDir);
4742
5158
  if (typeof js !== "string") return task.behaviors.map((b) => ({ id: b.id, pass: false, detail: js.error }));
4743
- const css = ["tokens.css", "styles.css"].map((f) => path15.join(bundleDir, f)).filter((f) => existsSync10(f)).map((f) => readFileSync7(f, "utf8")).join("\n");
5159
+ const css = candidateCss(bundleDir);
4744
5160
  const server = await chromium3.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
4745
5161
  const browser = await chromium3.connect(server.wsEndpoint());
4746
5162
  const results = [];
@@ -4851,6 +5267,7 @@ var init_behavior = __esm({
4851
5267
  "use strict";
4852
5268
  init_runtime();
4853
5269
  init_browser();
5270
+ init_candidate_css();
4854
5271
  init_font_faces();
4855
5272
  init_image_diff();
4856
5273
  init_mount_limits();
@@ -4883,8 +5300,8 @@ var init_behavior = __esm({
4883
5300
  });
4884
5301
 
4885
5302
  // packages/verify/src/bundle-quality.ts
4886
- import { readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync5, existsSync as existsSync11 } from "node:fs";
4887
- import path16 from "node:path";
5303
+ import { readFileSync as readFileSync10, rmSync as rmSync2, writeFileSync as writeFileSync5, existsSync as existsSync13 } from "node:fs";
5304
+ import path18 from "node:path";
4888
5305
  function definedVars(tokensCss) {
4889
5306
  if (tokensCss === void 0) return void 0;
4890
5307
  const names = /* @__PURE__ */ new Set();
@@ -4893,19 +5310,19 @@ function definedVars(tokensCss) {
4893
5310
  }
4894
5311
  function recordedTokenMapState(setDir, reps) {
4895
5312
  const readMap = (file) => {
4896
- if (!existsSync11(file)) return void 0;
5313
+ if (!existsSync13(file)) return void 0;
4897
5314
  try {
4898
- const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync8(file, "utf8"))) || "{}");
5315
+ const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync10(file, "utf8"))) || "{}");
4899
5316
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
4900
5317
  } catch {
4901
5318
  return {};
4902
5319
  }
4903
5320
  };
4904
- const setLevel = readMap(path16.join(setDir, "get_variable_defs.json"));
5321
+ const setLevel = readMap(path18.join(setDir, "get_variable_defs.json"));
4905
5322
  if (setLevel !== void 0) return Object.keys(setLevel).length === 0 ? "empty" : "populated";
4906
5323
  let recorded = false;
4907
5324
  for (const rep of reps) {
4908
- const m = readMap(path16.join(setDir, rep, "get_variable_defs.json"));
5325
+ const m = readMap(path18.join(setDir, rep, "get_variable_defs.json"));
4909
5326
  if (m === void 0) continue;
4910
5327
  recorded = true;
4911
5328
  if (Object.keys(m).length > 0) return "populated";
@@ -4970,11 +5387,11 @@ function fontStackFindings(sheets, coverage) {
4970
5387
  }
4971
5388
  async function checkBundleQuality(bundleDir, entry, set, fontManifest) {
4972
5389
  const findings = [];
4973
- const entryPath = path16.join(bundleDir, entry);
4974
- const cssPath = path16.join(bundleDir, "styles.css");
4975
- const tokensPath = path16.join(bundleDir, "tokens.css");
4976
- const css = existsSync11(cssPath) ? readFileSync8(cssPath, "utf8") : "";
4977
- const tokensCss = existsSync11(tokensPath) ? readFileSync8(tokensPath, "utf8") : void 0;
5390
+ const entryPath = path18.join(bundleDir, entry);
5391
+ const cssPath = path18.join(bundleDir, "styles.css");
5392
+ const tokensPath = path18.join(bundleDir, "tokens.css");
5393
+ const css = existsSync13(cssPath) ? readFileSync10(cssPath, "utf8") : "";
5394
+ const tokensCss = existsSync13(tokensPath) ? readFileSync10(tokensPath, "utf8") : void 0;
4978
5395
  findings.push(
4979
5396
  ...fontStackFindings(
4980
5397
  [
@@ -4984,11 +5401,11 @@ async function checkBundleQuality(bundleDir, entry, set, fontManifest) {
4984
5401
  injectedFamilyWeights(fontManifest)
4985
5402
  )
4986
5403
  );
4987
- if (existsSync11(entryPath)) {
5404
+ if (existsSync13(entryPath)) {
4988
5405
  const workDir = newScratchDir("quality");
4989
5406
  try {
4990
- const tsxPath = path16.join(workDir, entry);
4991
- writeFileSync5(tsxPath, readFileSync8(entryPath, "utf8"));
5407
+ const tsxPath = path18.join(workDir, entry);
5408
+ writeFileSync5(tsxPath, readFileSync10(entryPath, "utf8"));
4992
5409
  for (const d of runTscStrict([tsxPath]).diagnostics) {
4993
5410
  findings.push({ kind: "tsc", file: entry, ...d.line === void 0 ? {} : { line: d.line }, message: `TS${d.code}: ${d.message}` });
4994
5411
  }
@@ -5063,8 +5480,8 @@ var init_effect_geometry = __esm({
5063
5480
  });
5064
5481
 
5065
5482
  // packages/verify/src/bundle-score.ts
5066
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
5067
- import path17 from "node:path";
5483
+ import { existsSync as existsSync14, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
5484
+ import path19 from "node:path";
5068
5485
  import { build as build4 } from "esbuild";
5069
5486
  import { chromium as chromium4 } from "playwright-core";
5070
5487
  function getFontFaces2() {
@@ -5118,7 +5535,7 @@ function chooseSeat(cropAt, ref, padW, padH, backdrop, origin, extents) {
5118
5535
  }
5119
5536
  function metadataRoot(set, rep) {
5120
5537
  try {
5121
- const text = JSON.parse(readFileSync9(path17.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
5538
+ const text = JSON.parse(readFileSync11(path19.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
5122
5539
  return parseMetadataStructure(text);
5123
5540
  } catch {
5124
5541
  return void 0;
@@ -5173,19 +5590,19 @@ function smallSemanticNodes(set, rep, maxArea = 1024) {
5173
5590
  });
5174
5591
  }
5175
5592
  function repMeta(set, rep) {
5176
- const text = JSON.parse(readFileSync9(path17.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
5593
+ const text = JSON.parse(readFileSync11(path19.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
5177
5594
  const root = parseMetadataStructure(text);
5178
5595
  return { w: Math.round(root.width ?? 100), h: Math.round(root.height ?? 40) };
5179
5596
  }
5180
5597
  function repRef(set, rep) {
5181
- const env = JSON.parse(readFileSync9(path17.join(set, rep, "get_screenshot.json"), "utf8")).content.find((c) => c.type === "image");
5598
+ const env = JSON.parse(readFileSync11(path19.join(set, rep, "get_screenshot.json"), "utf8")).content.find((c) => c.type === "image");
5182
5599
  return Uint8Array.from(Buffer.from(env?.data ?? "", "base64"));
5183
5600
  }
5184
5601
  function repEffectExtents(set, rep) {
5185
- const file = path17.join(set, rep, "get_design_context.json");
5186
- if (!existsSync12(file)) return void 0;
5602
+ const file = path19.join(set, rep, "get_design_context.json");
5603
+ if (!existsSync14(file)) return void 0;
5187
5604
  try {
5188
- const text = JSON.parse(readFileSync9(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
5605
+ const text = JSON.parse(readFileSync11(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
5189
5606
  const extents = shadowExtents(text);
5190
5607
  return extents.top + extents.right + extents.bottom + extents.left > 0 ? extents : void 0;
5191
5608
  } catch {
@@ -5196,13 +5613,13 @@ async function scoreBundleForTask(task, bundleDir, bar = BAR, opts = {}) {
5196
5613
  if (opts.evidenceDir !== void 0) mkdirSync3(opts.evidenceDir, { recursive: true });
5197
5614
  const CONFIGS2 = task.configs;
5198
5615
  const timeoutMs = opts.mountDeadlineMs ?? ENGINE_MOUNT_DEADLINE_MS;
5199
- const entryTsx = path17.join(bundleDir, task.entry);
5200
- if (!existsSync12(entryTsx)) return CONFIGS2.map((c) => ({ rep: c.rep, similarity: 0, inkRecall: 0, exact: { similarity: 0, inkRecall: 0 }, pass: false, error: `${task.entry} missing` }));
5201
- const css = ["tokens.css", "styles.css"].map((f) => path17.join(bundleDir, f)).filter((f) => existsSync12(f)).map((f) => readFileSync9(f, "utf8")).join("\n");
5616
+ const entryTsx = path19.join(bundleDir, task.entry);
5617
+ if (!existsSync14(entryTsx)) return CONFIGS2.map((c) => ({ rep: c.rep, similarity: 0, inkRecall: 0, exact: { similarity: 0, inkRecall: 0 }, pass: false, error: `${task.entry} missing` }));
5618
+ const css = candidateCss(bundleDir);
5202
5619
  const mountSrc = `
5203
5620
  import { createElement } from "react";
5204
5621
  import { createRoot } from "react-dom/client";
5205
- import * as B from ${JSON.stringify(path17.resolve(entryTsx))};
5622
+ import * as B from ${JSON.stringify(path19.resolve(entryTsx))};
5206
5623
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
5207
5624
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
5208
5625
  const root = document.getElementById("root");
@@ -5297,12 +5714,12 @@ body{margin:0;background:rgb(${backdrop.join(",")})}
5297
5714
  return name === void 0 ? c : { ...c, name };
5298
5715
  });
5299
5716
  if (opts.evidenceDir !== void 0) {
5300
- writeFileSync6(path17.join(opts.evidenceDir, `${cfg.rep}-render.png`), scored);
5301
- writeFileSync6(path17.join(opts.evidenceDir, `${cfg.rep}-ref.png`), ref);
5302
- writeFileSync6(path17.join(opts.evidenceDir, `${cfg.rep}-diff.png`), diffPng(scored, ref));
5717
+ writeFileSync6(path19.join(opts.evidenceDir, `${cfg.rep}-render.png`), scored);
5718
+ writeFileSync6(path19.join(opts.evidenceDir, `${cfg.rep}-ref.png`), ref);
5719
+ writeFileSync6(path19.join(opts.evidenceDir, `${cfg.rep}-diff.png`), diffPng(scored, ref));
5303
5720
  for (const [i, c] of absent.slice(0, 3).entries()) {
5304
- writeFileSync6(path17.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-ref.png`), zoomCrop(ref, c));
5305
- writeFileSync6(path17.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-render.png`), zoomCrop(scored, c));
5721
+ writeFileSync6(path19.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-ref.png`), zoomCrop(ref, c));
5722
+ writeFileSync6(path19.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-render.png`), zoomCrop(scored, c));
5306
5723
  }
5307
5724
  }
5308
5725
  return {
@@ -5366,6 +5783,7 @@ var init_bundle_score = __esm({
5366
5783
  "use strict";
5367
5784
  init_runtime();
5368
5785
  init_browser();
5786
+ init_candidate_css();
5369
5787
  init_src();
5370
5788
  init_effect_geometry();
5371
5789
  init_image_diff();
@@ -5410,8 +5828,6 @@ var init_prelude = __esm({
5410
5828
  });
5411
5829
 
5412
5830
  // packages/verify/src/parity.ts
5413
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "node:fs";
5414
- import path18 from "node:path";
5415
5831
  import { chromium as chromium6 } from "playwright-core";
5416
5832
  function getFontFaces3() {
5417
5833
  _fontFaces3 ??= fontFaceCss();
@@ -5440,7 +5856,7 @@ async function checkHoverParity(task, bundleDir, authority, opts = {}) {
5440
5856
  const deadlineMs = timeoutMs + 1e4;
5441
5857
  const js = await compileMount(task, bundleDir);
5442
5858
  if (typeof js !== "string") return configs.map((c) => ({ id: `parity:${c.rep}`, pass: false, detail: js.error }));
5443
- const css = ["tokens.css", "styles.css"].map((f) => path18.join(bundleDir, f)).filter((f) => existsSync13(f)).map((f) => readFileSync10(f, "utf8")).join("\n");
5859
+ const css = candidateCss(bundleDir);
5444
5860
  const server = await chromium6.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
5445
5861
  const browser = await chromium6.connect(server.wsEndpoint());
5446
5862
  const results = [];
@@ -5537,6 +5953,7 @@ var init_parity = __esm({
5537
5953
  "packages/verify/src/parity.ts"() {
5538
5954
  "use strict";
5539
5955
  init_browser();
5956
+ init_candidate_css();
5540
5957
  init_behavior();
5541
5958
  init_font_faces();
5542
5959
  init_mount_limits();
@@ -5546,8 +5963,8 @@ var init_parity = __esm({
5546
5963
 
5547
5964
  // packages/verify/src/composition.ts
5548
5965
  import { createRequire as createRequire2 } from "node:module";
5549
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "node:fs";
5550
- import path19 from "node:path";
5966
+ import { existsSync as existsSync15 } from "node:fs";
5967
+ import path20 from "node:path";
5551
5968
  import { build as build6 } from "esbuild";
5552
5969
  import { chromium as chromium7 } from "playwright-core";
5553
5970
  function getFontFaces4() {
@@ -5555,9 +5972,9 @@ function getFontFaces4() {
5555
5972
  return _fontFaces4;
5556
5973
  }
5557
5974
  async function compileInstrumentedMount(task, bundleDir) {
5558
- const entryTsx = path19.join(bundleDir, task.entry);
5559
- if (!existsSync14(entryTsx)) return { error: `${task.entry} missing` };
5560
- const requireFromVerify = createRequire2(path19.join(VERIFY_PKG_DIR, "package.json"));
5975
+ const entryTsx = path20.join(bundleDir, task.entry);
5976
+ if (!existsSync15(entryTsx)) return { error: `${task.entry} missing` };
5977
+ const requireFromVerify = createRequire2(path20.join(VERIFY_PKG_DIR, "package.json"));
5561
5978
  let realJsxPath;
5562
5979
  try {
5563
5980
  realJsxPath = requireFromVerify.resolve("react/jsx-runtime");
@@ -5568,7 +5985,7 @@ async function compileInstrumentedMount(task, bundleDir) {
5568
5985
  import { createElement } from "react";
5569
5986
  import { createRoot } from "react-dom/client";
5570
5987
  import { __registerParts } from "react/jsx-runtime";
5571
- import * as B from ${JSON.stringify(path19.resolve(entryTsx))};
5988
+ import * as B from ${JSON.stringify(path20.resolve(entryTsx))};
5572
5989
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; partComponents: string[] } }).__cfg;
5573
5990
  const pairs: Array<[unknown, string]> = [];
5574
5991
  for (const name of cfg.partComponents) {
@@ -5623,7 +6040,7 @@ function expectedParts(task, roles, mainSlug) {
5623
6040
  }
5624
6041
  function interiorRegions(setDir, roles) {
5625
6042
  const mains = roles.main;
5626
- const withInterior = mains.filter((m) => existsSync14(path19.join(setDir, m, "get_metadata_interior.json")));
6043
+ const withInterior = mains.filter((m) => existsSync15(path20.join(setDir, m, "get_metadata_interior.json")));
5627
6044
  if (withInterior.length === 0) {
5628
6045
  return { unavailable: "no interior geometry recorded for any main (pre-obligation set) \u2014 re-record with `tendril record` to enable crop checks" };
5629
6046
  }
@@ -5640,7 +6057,7 @@ async function checkCropComposition(task, bundleDir, regions, opts = {}) {
5640
6057
  if (typeof js !== "string") {
5641
6058
  return regions.map((r) => ({ id: cropId(r), pass: false, similarity: 0, inkRecall: 0, detail: js.error }));
5642
6059
  }
5643
- const css = ["tokens.css", "styles.css"].map((f) => path19.join(bundleDir, f)).filter((f) => existsSync14(f)).map((f) => readFileSync11(f, "utf8")).join("\n");
6060
+ const css = candidateCss(bundleDir);
5644
6061
  const server = await chromium7.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
5645
6062
  const browser = await chromium7.connect(server.wsEndpoint());
5646
6063
  const PAD = 4;
@@ -5745,7 +6162,7 @@ async function checkStructuralComposition(task, bundleDir, roles, opts = {}) {
5745
6162
  const deadlineMs = timeoutMs + 1e4;
5746
6163
  const js = await compileInstrumentedMount(task, bundleDir);
5747
6164
  if (typeof js !== "string") return [...results, ...mains.map((m) => ({ id: `composition:${m}`, pass: false, detail: js.error }))];
5748
- const css = ["tokens.css", "styles.css"].map((f) => path19.join(bundleDir, f)).filter((f) => existsSync14(f)).map((f) => readFileSync11(f, "utf8")).join("\n");
6165
+ const css = candidateCss(bundleDir);
5749
6166
  const server = await chromium7.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
5750
6167
  const browser = await chromium7.connect(server.wsEndpoint());
5751
6168
  try {
@@ -5814,6 +6231,7 @@ var init_composition = __esm({
5814
6231
  "use strict";
5815
6232
  init_runtime();
5816
6233
  init_browser();
6234
+ init_candidate_css();
5817
6235
  init_font_faces();
5818
6236
  init_image_diff();
5819
6237
  init_mount_limits();
@@ -5835,17 +6253,17 @@ export const jsxs = (t, p, k) => real.jsxs(t, inject(t, p), k);
5835
6253
  });
5836
6254
 
5837
6255
  // packages/verify/src/occlusion.ts
5838
- import { existsSync as existsSync15 } from "node:fs";
5839
- import path20 from "node:path";
6256
+ import { existsSync as existsSync16 } from "node:fs";
6257
+ import path21 from "node:path";
5840
6258
  import { build as build7 } from "esbuild";
5841
6259
  import { chromium as chromium8 } from "playwright-core";
5842
6260
  async function compileTwoUp(task, bundleDir) {
5843
- const entryTsx = path20.join(bundleDir, task.entry);
5844
- if (!existsSync15(entryTsx)) return { error: `${task.entry} missing` };
6261
+ const entryTsx = path21.join(bundleDir, task.entry);
6262
+ if (!existsSync16(entryTsx)) return { error: `${task.entry} missing` };
5845
6263
  const src = `
5846
6264
  import { createElement } from "react";
5847
6265
  import { createRoot } from "react-dom/client";
5848
- import * as B from ${JSON.stringify(path20.resolve(entryTsx))};
6266
+ import * as B from ${JSON.stringify(path21.resolve(entryTsx))};
5849
6267
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
5850
6268
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
5851
6269
  for (const id of ["first", "second"]) {
@@ -6019,32 +6437,33 @@ var init_src4 = __esm({
6019
6437
  init_font_resolve();
6020
6438
  init_paths();
6021
6439
  init_parity();
6440
+ init_candidate_css();
6022
6441
  init_composition();
6023
6442
  init_occlusion();
6024
6443
  }
6025
6444
  });
6026
6445
 
6027
6446
  // packages/cli/src/environment.ts
6028
- import { existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
6029
- import path21 from "node:path";
6030
- import { createHash as createHash3 } from "node:crypto";
6447
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
6448
+ import path22 from "node:path";
6449
+ import { createHash as createHash4 } from "node:crypto";
6031
6450
  import { fileURLToPath as fileURLToPath5 } from "node:url";
6032
6451
  function cliVersion() {
6033
6452
  try {
6034
- return JSON.parse(readFileSync12(path21.join(path21.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
6453
+ return JSON.parse(readFileSync13(path22.join(path22.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
6035
6454
  } catch {
6036
6455
  return "dev";
6037
6456
  }
6038
6457
  }
6039
6458
  function environmentStamp(taskFamilies) {
6040
- const manifestPath2 = path21.join(fontCacheDir(), "manifest.json");
6459
+ const manifestPath2 = path22.join(fontCacheDir(), "manifest.json");
6041
6460
  let fontsHash = null;
6042
- if (existsSync16(manifestPath2)) {
6461
+ if (existsSync17(manifestPath2)) {
6043
6462
  try {
6044
- const entries = JSON.parse(readFileSync12(manifestPath2, "utf8"));
6463
+ const entries = JSON.parse(readFileSync13(manifestPath2, "utf8"));
6045
6464
  const scoped = taskFamilies === void 0 || taskFamilies === null ? entries : entries.filter((f) => taskFamilies.some((fam) => fam.toLowerCase() === f.family.toLowerCase()));
6046
6465
  const faces = scoped.map((f) => `${f.family}:${f.weight}:${f.sha256}`).sort();
6047
- fontsHash = faces.length === 0 ? null : createHash3("sha256").update(faces.join("\n")).digest("hex").slice(0, 16);
6466
+ fontsHash = faces.length === 0 ? null : createHash4("sha256").update(faces.join("\n")).digest("hex").slice(0, 16);
6048
6467
  } catch {
6049
6468
  fontsHash = null;
6050
6469
  }
@@ -6085,8 +6504,8 @@ var init_describe = __esm({
6085
6504
  });
6086
6505
 
6087
6506
  // packages/cli/src/env.ts
6088
- import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
6089
- import path22 from "node:path";
6507
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
6508
+ import path23 from "node:path";
6090
6509
  function parseEnv(content) {
6091
6510
  const entries = /* @__PURE__ */ new Map();
6092
6511
  for (const line of content.split("\n")) {
@@ -6098,9 +6517,9 @@ function parseEnv(content) {
6098
6517
  function resolveCredential(name) {
6099
6518
  const fromProcess = process.env[name];
6100
6519
  if (fromProcess) return fromProcess;
6101
- const envPath = path22.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
6102
- if (!existsSync17(envPath)) return void 0;
6103
- return parseEnv(readFileSync13(envPath, "utf8")).get(name);
6520
+ const envPath = path23.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
6521
+ if (!existsSync18(envPath)) return void 0;
6522
+ return parseEnv(readFileSync14(envPath, "utf8")).get(name);
6104
6523
  }
6105
6524
  var init_env = __esm({
6106
6525
  "packages/cli/src/env.ts"() {
@@ -6160,17 +6579,17 @@ var init_output = __esm({
6160
6579
  });
6161
6580
 
6162
6581
  // packages/cli/src/entitlement.ts
6163
- import { chmodSync, existsSync as existsSync18, mkdirSync as mkdirSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "node:fs";
6582
+ import { chmodSync, existsSync as existsSync19, mkdirSync as mkdirSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "node:fs";
6164
6583
  import crypto from "node:crypto";
6165
6584
  import os4 from "node:os";
6166
- import path23 from "node:path";
6585
+ import path24 from "node:path";
6167
6586
  function entitlementPath() {
6168
- return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path23.join(os4.homedir(), ".tendril", "entitlement.json");
6587
+ return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path24.join(os4.homedir(), ".tendril", "entitlement.json");
6169
6588
  }
6170
6589
  function readStoredEntitlement(file = entitlementPath()) {
6171
- if (!existsSync18(file)) return void 0;
6590
+ if (!existsSync19(file)) return void 0;
6172
6591
  try {
6173
- const parsed = JSON.parse(readFileSync14(file, "utf8"));
6592
+ const parsed = JSON.parse(readFileSync15(file, "utf8"));
6174
6593
  if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
6175
6594
  return { token: parsed.token, lastRefreshAt: parsed.lastRefreshAt };
6176
6595
  } catch {
@@ -6178,7 +6597,7 @@ function readStoredEntitlement(file = entitlementPath()) {
6178
6597
  }
6179
6598
  }
6180
6599
  function writeStoredEntitlement(stored, file = entitlementPath()) {
6181
- mkdirSync4(path23.dirname(file), { recursive: true });
6600
+ mkdirSync4(path24.dirname(file), { recursive: true });
6182
6601
  writeFileSync7(file, `${JSON.stringify(stored, null, 2)}
6183
6602
  `);
6184
6603
  chmodSync(file, 384);
@@ -6263,9 +6682,9 @@ var init_entitlement = __esm({
6263
6682
 
6264
6683
  // packages/cli/src/commands/doctor.ts
6265
6684
  import { spawnSync } from "node:child_process";
6266
- import { existsSync as existsSync19, readFileSync as readFileSync15, readdirSync as readdirSync4 } from "node:fs";
6685
+ import { existsSync as existsSync20, readFileSync as readFileSync16, readdirSync as readdirSync6 } from "node:fs";
6267
6686
  import os5 from "node:os";
6268
- import path24 from "node:path";
6687
+ import path25 from "node:path";
6269
6688
  function withDeadline(work, ms) {
6270
6689
  return Promise.race([
6271
6690
  work,
@@ -6325,19 +6744,19 @@ async function runDoctorChecks(options) {
6325
6744
  remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
6326
6745
  });
6327
6746
  }
6328
- const fontManifest = path24.join(fontCacheDir(), "manifest.json");
6747
+ const fontManifest = path25.join(fontCacheDir(), "manifest.json");
6329
6748
  checks.push(
6330
- existsSync19(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync15(fontManifest, "utf8")).length} faces)` } : {
6749
+ existsSync20(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync16(fontManifest, "utf8")).length} faces)` } : {
6331
6750
  name: "font-cache",
6332
6751
  ok: true,
6333
6752
  detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
6334
6753
  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.`
6335
6754
  }
6336
6755
  );
6337
- const pluginRoot = path24.join(os5.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
6338
- if (existsSync19(pluginRoot)) {
6756
+ const pluginRoot = path25.join(os5.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
6757
+ if (existsSync20(pluginRoot)) {
6339
6758
  try {
6340
- const versions = readdirSync4(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
6759
+ const versions = readdirSync6(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
6341
6760
  const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
6342
6761
  if (newest !== void 0) {
6343
6762
  const skewed = versionIsNewer(newest, cliVersion());
@@ -6482,7 +6901,7 @@ var init_doctor = __esm({
6482
6901
  });
6483
6902
 
6484
6903
  // packages/llm/src/model-config.ts
6485
- import { z as z6 } from "zod";
6904
+ import { z as z7 } from "zod";
6486
6905
  function resolveModel(config, requestedId) {
6487
6906
  const entry = config.allowlist.find((m) => m.id === requestedId);
6488
6907
  if (entry) return { ok: true, entry };
@@ -6504,46 +6923,46 @@ var ModelStatusSchema, ModelEntrySchema, ModelConfigSchema, DEFAULT_MODEL_CONFIG
6504
6923
  var init_model_config = __esm({
6505
6924
  "packages/llm/src/model-config.ts"() {
6506
6925
  "use strict";
6507
- ModelStatusSchema = z6.enum(["verified", "degraded"]);
6508
- ModelEntrySchema = z6.object({
6926
+ ModelStatusSchema = z7.enum(["verified", "degraded"]);
6927
+ ModelEntrySchema = z7.object({
6509
6928
  /** OpenRouter model id, e.g. "deepseek/deepseek-v4-flash". */
6510
- id: z6.string(),
6929
+ id: z7.string(),
6511
6930
  status: ModelStatusSchema,
6512
6931
  /** Roles this model may fill in the pipeline. */
6513
- roles: z6.array(z6.enum(["bulk", "polish"])),
6932
+ roles: z7.array(z7.enum(["bulk", "polish"])),
6514
6933
  /** Accepts image input. Text-only models get the screenshot dropped with a
6515
6934
  * warning instead of a provider 404 (Carbon run 1 finding). */
6516
- vision: z6.boolean().default(false),
6935
+ vision: z7.boolean().default(false),
6517
6936
  /** Engine-adapter params shipped WITH the entry (ADR-011 §2): the
6518
6937
  * eval-proven settings ride the allowlist so users never rediscover a
6519
6938
  * model's quirks at their own expense (K2.7-code burned its whole
6520
6939
  * completion budget on mandatory reasoning and emitted nothing). */
6521
- adapter: z6.object({
6940
+ adapter: z7.object({
6522
6941
  /** Minimum completion budget the model needs to emit full files. */
6523
- maxTokens: z6.number().optional(),
6942
+ maxTokens: z7.number().optional(),
6524
6943
  /** "mandatory": provider refuses reasoning-off; budget accordingly. */
6525
- reasoning: z6.enum(["mandatory", "optional", "none"]).optional()
6944
+ reasoning: z7.enum(["mandatory", "optional", "none"]).optional()
6526
6945
  }).optional(),
6527
6946
  /** One-line pointer to the evidence behind `status` (which eval, when).
6528
6947
  * Pricing intentionally has no static field — the resolution path is
6529
6948
  * the provider's models endpoint at run time (ADR-011 §2). */
6530
- evidence: z6.string().optional()
6949
+ evidence: z7.string().optional()
6531
6950
  });
6532
- ModelConfigSchema = z6.object({
6951
+ ModelConfigSchema = z7.object({
6533
6952
  /** Default model for codegen + repair (pipeline steps 4–5, facts path). */
6534
- bulk: z6.string(),
6953
+ bulk: z7.string(),
6535
6954
  /** Optional premium polish model (pipeline step 6, aesthetics/naming only). */
6536
- polish: z6.string().optional(),
6955
+ polish: z7.string().optional(),
6537
6956
  /** ADR-011 §2 default-model pointer for the curated recorded-truth
6538
6957
  * engine (a separate axis from the facts-era `bulk`; `generate` flips
6539
6958
  * to it in ADR-010 slice 6). Moves only via the reference-corpus gate. */
6540
- curatedDefault: z6.string().optional(),
6959
+ curatedDefault: z7.string().optional(),
6541
6960
  /** ADR-011 §3a Rung-1 escalation target: the model the curated loop
6542
6961
  * offers for configs still sub-bar at plateau. Designated ONLY via
6543
6962
  * the register's gate (a real curated cert-target run on the measured
6544
6963
  * escalation case); empty means no Rung-1 offer, never a default. */
6545
- escalationTarget: z6.string().optional(),
6546
- allowlist: z6.array(ModelEntrySchema)
6964
+ escalationTarget: z7.string().optional(),
6965
+ allowlist: z7.array(ModelEntrySchema)
6547
6966
  });
6548
6967
  DEFAULT_MODEL_CONFIG = {
6549
6968
  bulk: "google/gemini-3.1-flash-lite",
@@ -6722,7 +7141,7 @@ var init_openrouter = __esm({
6722
7141
  });
6723
7142
 
6724
7143
  // packages/llm/src/semantics.ts
6725
- import { z as z7 } from "zod";
7144
+ import { z as z8 } from "zod";
6726
7145
  function portable(node) {
6727
7146
  if (Array.isArray(node)) return node.map(portable);
6728
7147
  if (typeof node !== "object" || node === null) return node;
@@ -6746,7 +7165,7 @@ function portable(node) {
6746
7165
  return out;
6747
7166
  }
6748
7167
  function toPortableJsonSchema(schema) {
6749
- return portable(z7.toJSONSchema(schema));
7168
+ return portable(z8.toJSONSchema(schema));
6750
7169
  }
6751
7170
  function semanticsJsonSchema() {
6752
7171
  return toPortableJsonSchema(SemanticsSchema);
@@ -6761,18 +7180,18 @@ var PropTypeSchema, SemanticsSchema, CodegenOutputSchema, DocsOutputSchema;
6761
7180
  var init_semantics = __esm({
6762
7181
  "packages/llm/src/semantics.ts"() {
6763
7182
  "use strict";
6764
- PropTypeSchema = z7.discriminatedUnion("kind", [
6765
- z7.object({ kind: z7.literal("enum"), values: z7.array(z7.string()).min(1) }),
6766
- z7.object({ kind: z7.literal("boolean") }),
6767
- z7.object({ kind: z7.literal("string") }),
6768
- z7.object({ kind: z7.literal("reactNode") }),
6769
- z7.object({ kind: z7.literal("handler") })
7183
+ PropTypeSchema = z8.discriminatedUnion("kind", [
7184
+ z8.object({ kind: z8.literal("enum"), values: z8.array(z8.string()).min(1) }),
7185
+ z8.object({ kind: z8.literal("boolean") }),
7186
+ z8.object({ kind: z8.literal("string") }),
7187
+ z8.object({ kind: z8.literal("reactNode") }),
7188
+ z8.object({ kind: z8.literal("handler") })
6770
7189
  ]);
6771
- SemanticsSchema = z7.object({
7190
+ SemanticsSchema = z8.object({
6772
7191
  /** PascalCase component name. */
6773
- componentName: z7.string().regex(/^[A-Z][A-Za-z0-9]*$/),
7192
+ componentName: z8.string().regex(/^[A-Z][A-Za-z0-9]*$/),
6774
7193
  /** Semantic HTML element for the root node. */
6775
- element: z7.enum([
7194
+ element: z8.enum([
6776
7195
  "button",
6777
7196
  "a",
6778
7197
  "div",
@@ -6786,33 +7205,33 @@ var init_semantics = __esm({
6786
7205
  "ul",
6787
7206
  "li"
6788
7207
  ]),
6789
- props: z7.array(
6790
- z7.object({
6791
- name: z7.string().regex(/^[a-z][A-Za-z0-9]*$/),
7208
+ props: z8.array(
7209
+ z8.object({
7210
+ name: z8.string().regex(/^[a-z][A-Za-z0-9]*$/),
6792
7211
  type: PropTypeSchema,
6793
- required: z7.boolean(),
7212
+ required: z8.boolean(),
6794
7213
  /** nullish, not optional: models routinely encode "no default" as
6795
7214
  * null (observed live: kimi-k2.7-code, Carbon run 8). */
6796
- defaultValue: z7.string().nullish()
7215
+ defaultValue: z8.string().nullish()
6797
7216
  })
6798
7217
  ),
6799
7218
  /** Variant axes that become discriminated unions (invalid combos must not compile). */
6800
- discriminatedUnions: z7.array(
6801
- z7.object({
6802
- discriminant: z7.string(),
6803
- arms: z7.array(
6804
- z7.object({
6805
- value: z7.string(),
7219
+ discriminatedUnions: z8.array(
7220
+ z8.object({
7221
+ discriminant: z8.string(),
7222
+ arms: z8.array(
7223
+ z8.object({
7224
+ value: z8.string(),
6806
7225
  /** Props only valid for this arm. */
6807
- extraProps: z7.array(z7.string())
7226
+ extraProps: z8.array(z8.string())
6808
7227
  })
6809
7228
  )
6810
7229
  })
6811
7230
  ),
6812
- a11y: z7.object({
6813
- role: z7.string().nullish(),
6814
- keyboard: z7.array(z7.string()),
6815
- aria: z7.array(z7.string())
7231
+ a11y: z8.object({
7232
+ role: z8.string().nullish(),
7233
+ keyboard: z8.array(z8.string()),
7234
+ aria: z8.array(z8.string())
6816
7235
  }),
6817
7236
  /**
6818
7237
  * How each Figma axis value is realized through the generated API —
@@ -6821,29 +7240,29 @@ var init_semantics = __esm({
6821
7240
  * The visual fact check verifies these claims against real renders, so a
6822
7241
  * wrong mapping fails measurement instead of being trusted.
6823
7242
  */
6824
- variantMapping: z7.array(
6825
- z7.object({
6826
- axis: z7.string(),
6827
- value: z7.string(),
6828
- prop: z7.string(),
6829
- propValue: z7.union([z7.string(), z7.boolean()])
7243
+ variantMapping: z8.array(
7244
+ z8.object({
7245
+ axis: z8.string(),
7246
+ value: z8.string(),
7247
+ prop: z8.string(),
7248
+ propValue: z8.union([z8.string(), z8.boolean()])
6830
7249
  })
6831
7250
  )
6832
7251
  });
6833
- CodegenOutputSchema = z7.object({
7252
+ CodegenOutputSchema = z8.object({
6834
7253
  /** Component source (.tsx). Token-only styling via the provided CSS file. */
6835
- tsx: z7.string().min(1),
7254
+ tsx: z8.string().min(1),
6836
7255
  /** Stylesheet (.css) — themable properties reference var(--token) only. */
6837
- css: z7.string().min(1)
7256
+ css: z8.string().min(1)
6838
7257
  });
6839
- DocsOutputSchema = z7.object({
6840
- usageMd: z7.string().min(1),
6841
- aiHints: z7.object({ selectionCriteria: z7.array(z7.string()).min(1) }),
6842
- antiPatterns: z7.array(
6843
- z7.object({ scenario: z7.string(), reason: z7.string(), alternative: z7.string() })
7258
+ DocsOutputSchema = z8.object({
7259
+ usageMd: z8.string().min(1),
7260
+ aiHints: z8.object({ selectionCriteria: z8.array(z8.string()).min(1) }),
7261
+ antiPatterns: z8.array(
7262
+ z8.object({ scenario: z8.string(), reason: z8.string(), alternative: z8.string() })
6844
7263
  ),
6845
- composition: z7.array(z7.string()),
6846
- examples: z7.array(z7.object({ title: z7.string(), code: z7.string() }))
7264
+ composition: z8.array(z8.string()),
7265
+ examples: z8.array(z8.object({ title: z8.string(), code: z8.string() }))
6847
7266
  });
6848
7267
  }
6849
7268
  });
@@ -7313,56 +7732,56 @@ var init_src5 = __esm({
7313
7732
  });
7314
7733
 
7315
7734
  // packages/metadata/src/component-json.ts
7316
- import { z as z8 } from "zod";
7735
+ import { z as z9 } from "zod";
7317
7736
  var TokenUseSchema, AntiPatternSchema, ComponentJsonSchema;
7318
7737
  var init_component_json = __esm({
7319
7738
  "packages/metadata/src/component-json.ts"() {
7320
7739
  "use strict";
7321
- TokenUseSchema = z8.object({
7740
+ TokenUseSchema = z9.object({
7322
7741
  /** DTCG token path, e.g. "color.primary". */
7323
- path: z8.string(),
7324
- cssVar: z8.string(),
7325
- usage: z8.string()
7326
- });
7327
- AntiPatternSchema = z8.object({
7328
- scenario: z8.string(),
7329
- reason: z8.string(),
7330
- alternative: z8.string()
7331
- });
7332
- ComponentJsonSchema = z8.object({
7333
- name: z8.string(),
7334
- version: z8.string(),
7742
+ path: z9.string(),
7743
+ cssVar: z9.string(),
7744
+ usage: z9.string()
7745
+ });
7746
+ AntiPatternSchema = z9.object({
7747
+ scenario: z9.string(),
7748
+ reason: z9.string(),
7749
+ alternative: z9.string()
7750
+ });
7751
+ ComponentJsonSchema = z9.object({
7752
+ name: z9.string(),
7753
+ version: z9.string(),
7335
7754
  // --- Structural: generated from code by react-docgen. Never hand-edited. ---
7336
- props: z8.array(
7337
- z8.object({
7338
- name: z8.string(),
7339
- type: z8.string(),
7340
- required: z8.boolean(),
7341
- defaultValue: z8.string().optional(),
7342
- description: z8.string().optional()
7755
+ props: z9.array(
7756
+ z9.object({
7757
+ name: z9.string(),
7758
+ type: z9.string(),
7759
+ required: z9.boolean(),
7760
+ defaultValue: z9.string().optional(),
7761
+ description: z9.string().optional()
7343
7762
  })
7344
7763
  ),
7345
- variants: z8.record(z8.string(), z8.array(z8.string())),
7346
- states: z8.array(z8.string()),
7347
- tokensUsed: z8.array(TokenUseSchema),
7348
- a11y: z8.object({
7349
- role: z8.string().optional(),
7350
- keyboard: z8.array(z8.string()),
7351
- aria: z8.array(z8.string())
7764
+ variants: z9.record(z9.string(), z9.array(z9.string())),
7765
+ states: z9.array(z9.string()),
7766
+ tokensUsed: z9.array(TokenUseSchema),
7767
+ a11y: z9.object({
7768
+ role: z9.string().optional(),
7769
+ keyboard: z9.array(z9.string()),
7770
+ aria: z9.array(z9.string())
7352
7771
  }),
7353
7772
  // --- LLM-authored, deterministically validated. ---
7354
- aiHints: z8.object({
7355
- selectionCriteria: z8.array(z8.string())
7773
+ aiHints: z9.object({
7774
+ selectionCriteria: z9.array(z9.string())
7356
7775
  }),
7357
- antiPatterns: z8.array(AntiPatternSchema),
7358
- composition: z8.array(z8.string()),
7359
- examples: z8.array(z8.object({ title: z8.string(), code: z8.string() })),
7776
+ antiPatterns: z9.array(AntiPatternSchema),
7777
+ composition: z9.array(z9.string()),
7778
+ examples: z9.array(z9.object({ title: z9.string(), code: z9.string() })),
7360
7779
  // --- Provenance. ---
7361
- generated: z8.object({
7362
- tool: z8.literal("tendril"),
7363
- toolVersion: z8.string(),
7364
- model: z8.string(),
7365
- modelStatus: z8.enum(["verified", "degraded"])
7780
+ generated: z9.object({
7781
+ tool: z9.literal("tendril"),
7782
+ toolVersion: z9.string(),
7783
+ model: z9.string(),
7784
+ modelStatus: z9.enum(["verified", "degraded"])
7366
7785
  })
7367
7786
  });
7368
7787
  }
@@ -7448,7 +7867,7 @@ var init_extract = __esm({
7448
7867
  });
7449
7868
 
7450
7869
  // packages/metadata/src/recording-set.ts
7451
- import { z as z9 } from "zod";
7870
+ import { z as z10 } from "zod";
7452
7871
  function roleLossToken(loss) {
7453
7872
  return loss.kind === "main" ? `main:${loss.main}` : `part:${loss.part}${EDGE_ARROW}${loss.main}`;
7454
7873
  }
@@ -7511,58 +7930,58 @@ var init_recording_set = __esm({
7511
7930
  "packages/metadata/src/recording-set.ts"() {
7512
7931
  "use strict";
7513
7932
  RECORDING_SET_VERSION = 1;
7514
- EnvelopeSchema = z9.object({
7515
- content: z9.array(
7516
- z9.object({
7517
- type: z9.string().optional(),
7518
- text: z9.string().optional(),
7519
- data: z9.string().optional(),
7520
- mimeType: z9.string().optional()
7933
+ EnvelopeSchema = z10.object({
7934
+ content: z10.array(
7935
+ z10.object({
7936
+ type: z10.string().optional(),
7937
+ text: z10.string().optional(),
7938
+ data: z10.string().optional(),
7939
+ mimeType: z10.string().optional()
7521
7940
  })
7522
7941
  )
7523
7942
  });
7524
- RepEntrySchema = z9.object({
7943
+ RepEntrySchema = z10.object({
7525
7944
  /** Directory slug under the set root. */
7526
- slug: z9.string().min(1),
7945
+ slug: z10.string().min(1),
7527
7946
  /** Figma node id of the recorded symbol, e.g. "2222:10354". */
7528
- nodeId: z9.string().regex(/^\d+:\d+$/),
7947
+ nodeId: z10.string().regex(/^\d+:\d+$/),
7529
7948
  /** Source frame node id (multi-frame kits record where each symbol
7530
7949
  * lives — the shadcn Calendar spans four frames). */
7531
- sourceFrame: z9.string().regex(/^\d+:\d+$/).optional()
7950
+ sourceFrame: z10.string().regex(/^\d+:\d+$/).optional()
7532
7951
  });
7533
- RolesSchema = z9.object({
7952
+ RolesSchema = z10.object({
7534
7953
  /** Slugs of reps whose symbols are MAIN components. */
7535
- main: z9.array(z9.string()),
7954
+ main: z10.array(z10.string()),
7536
7955
  /** slug → the main slugs it is a part of. */
7537
- parts: z9.record(z9.string(), z9.object({ partOf: z9.array(z9.string()).min(1) })),
7956
+ parts: z10.record(z10.string(), z10.object({ partOf: z10.array(z10.string()).min(1) })),
7538
7957
  /** External/unrecorded component references discovered during
7539
7958
  * derivation — disclosed, never silently dropped (Calendar's Icon
7540
7959
  * Buttons; unrecorded base variants). */
7541
- external: z9.array(z9.string()).optional(),
7960
+ external: z10.array(z10.string()).optional(),
7542
7961
  /** Where this graph came from. A graph with empty `parts` makes the
7543
7962
  * structural composition check return an affirmative PASS, so a
7544
7963
  * human-authored override and a derived graph must never be
7545
7964
  * indistinguishable in a report. The CLI stamps this itself — a
7546
7965
  * supplied roles file cannot claim "derived" for itself. */
7547
- rolesSource: z9.enum(["derived", "human-override"]).optional(),
7966
+ rolesSource: z10.enum(["derived", "human-override"]).optional(),
7548
7967
  /** Losses in the DERIVED relation a human override deliberately
7549
7968
  * accepts, one token per loss (`roleLossToken`). Named one by one on
7550
7969
  * purpose: emptying out what composition measures — by dropping a
7551
7970
  * main, or by dropping/re-pointing a part→main edge — is exactly what
7552
7971
  * turns composition into a vacuous pass, so it is an acceptance
7553
7972
  * someone signs rather than an omission nobody notices. */
7554
- narrowingAccepted: z9.array(z9.string()).optional()
7973
+ narrowingAccepted: z10.array(z10.string()).optional()
7555
7974
  });
7556
7975
  EDGE_ARROW = "->";
7557
- RecordingSetManifestSchema = z9.object({
7558
- version: z9.literal(RECORDING_SET_VERSION),
7976
+ RecordingSetManifestSchema = z10.object({
7977
+ version: z10.literal(RECORDING_SET_VERSION),
7559
7978
  /** Human-readable component/system name. */
7560
- component: z9.string().min(1),
7979
+ component: z10.string().min(1),
7561
7980
  /** Source frames: node id → short description. */
7562
- sourceFrames: z9.record(z9.string(), z9.string()).optional(),
7563
- reps: z9.array(RepEntrySchema).min(1),
7981
+ sourceFrames: z10.record(z10.string(), z10.string()).optional(),
7982
+ reps: z10.array(RepEntrySchema).min(1),
7564
7983
  /** Honesty contract: what was NOT recorded, in prose. */
7565
- notRecorded: z9.string().optional(),
7984
+ notRecorded: z10.string().optional(),
7566
7985
  roles: RolesSchema.optional()
7567
7986
  });
7568
7987
  REQUIRED_REP_FILES = ["get_design_context.json", "get_metadata.json", "get_screenshot.json"];
@@ -7570,7 +7989,7 @@ var init_recording_set = __esm({
7570
7989
  });
7571
7990
 
7572
7991
  // packages/metadata/src/bundle.ts
7573
- import { z as z10 } from "zod";
7992
+ import { z as z11 } from "zod";
7574
7993
  function readBundleManifest(raw) {
7575
7994
  if (Buffer.byteLength(raw, "utf8") > MAX_BUNDLE_MANIFEST_BYTES) {
7576
7995
  return { issues: [{ severity: "error", message: `component.json exceeds the ${MAX_BUNDLE_MANIFEST_BYTES}-byte ingest cap` }] };
@@ -7642,94 +8061,94 @@ var init_bundle = __esm({
7642
8061
  "use strict";
7643
8062
  init_recording_set();
7644
8063
  BUNDLE_VERSION = 1;
7645
- ConfigStatusSchema = z10.enum(["certified", "pass", "fail", "fallback-unverified", "unverified", "part-unverified-standalone"]);
7646
- PinnedPropSchema = z10.object({
7647
- name: z10.string(),
7648
- type: z10.string(),
7649
- required: z10.boolean(),
7650
- default: z10.string().optional()
7651
- });
7652
- PropAdapterSchema = z10.record(
7653
- z10.string(),
7654
- z10.object({
7655
- component: z10.string(),
7656
- props: z10.record(z10.string(), z10.unknown())
8064
+ ConfigStatusSchema = z11.enum(["certified", "pass", "fail", "fallback-unverified", "unverified", "part-unverified-standalone"]);
8065
+ PinnedPropSchema = z11.object({
8066
+ name: z11.string(),
8067
+ type: z11.string(),
8068
+ required: z11.boolean(),
8069
+ default: z11.string().optional()
8070
+ });
8071
+ PropAdapterSchema = z11.record(
8072
+ z11.string(),
8073
+ z11.object({
8074
+ component: z11.string(),
8075
+ props: z11.record(z11.string(), z11.unknown())
7657
8076
  })
7658
8077
  );
7659
- RequiredFontSchema = z10.object({
7660
- family: z10.string(),
7661
- weight: z10.number(),
7662
- source: z10.string(),
7663
- sha256: z10.string()
7664
- });
7665
- ConfigClaimSchema = z10.object({
7666
- rep: z10.string(),
7667
- similarity: z10.number(),
7668
- inkRecall: z10.number(),
8078
+ RequiredFontSchema = z11.object({
8079
+ family: z11.string(),
8080
+ weight: z11.number(),
8081
+ source: z11.string(),
8082
+ sha256: z11.string()
8083
+ });
8084
+ ConfigClaimSchema = z11.object({
8085
+ rep: z11.string(),
8086
+ similarity: z11.number(),
8087
+ inkRecall: z11.number(),
7669
8088
  status: ConfigStatusSchema
7670
8089
  });
7671
- BehaviorClaimSchema = z10.object({
7672
- id: z10.string(),
7673
- pass: z10.boolean(),
7674
- detail: z10.string().optional()
8090
+ BehaviorClaimSchema = z11.object({
8091
+ id: z11.string(),
8092
+ pass: z11.boolean(),
8093
+ detail: z11.string().optional()
7675
8094
  });
7676
- BundleProvenanceSchema = z10.object({
8095
+ BundleProvenanceSchema = z11.object({
7677
8096
  /** Engine id that produced the accepted candidate. */
7678
- engine: z10.string(),
8097
+ engine: z11.string(),
7679
8098
  /** Resolved model id (curated engine) or agent identity, when known. */
7680
- model: z10.string().optional(),
7681
- recordingSet: z10.object({
8099
+ model: z11.string().optional(),
8100
+ recordingSet: z11.object({
7682
8101
  /** Set path as seen at generate time — informational, not trusted. */
7683
- path: z10.string(),
8102
+ path: z11.string(),
7684
8103
  /** Kit/component name from the set manifest. */
7685
- component: z10.string(),
8104
+ component: z11.string(),
7686
8105
  /** Founder-visible license posture of the recorded kit, if noted. */
7687
- licenseNote: z10.string().optional(),
8106
+ licenseNote: z11.string().optional(),
7688
8107
  /** Content hash over the set manifest + rep envelopes (the identity
7689
8108
  * verify compares against, not the path). */
7690
- hash: z10.string()
8109
+ hash: z11.string()
7691
8110
  }),
7692
- environment: z10.object({
7693
- chrome: z10.string(),
8111
+ environment: z11.object({
8112
+ chrome: z11.string(),
7694
8113
  // chromeVersion joined the stamp when a path alone proved
7695
8114
  // insufficient identity (two builds, one path, incomparable
7696
8115
  // numbers); optional so pre-epoch bundles keep validating.
7697
- chromeVersion: z10.string().nullable().optional(),
7698
- fontsManifestSha256: z10.string().nullable(),
8116
+ chromeVersion: z11.string().nullable().optional(),
8117
+ fontsManifestSha256: z11.string().nullable(),
7699
8118
  // Run 11: a bundle scored under a substitute face carried CSS
7700
8119
  // compensation (font-variation-settings tuned to the substitute)
7701
8120
  // with nothing marking it conditional — the provenance now names
7702
8121
  // the families that were substituted at scoring time. Optional so
7703
8122
  // pre-0.1.19 bundles keep validating; absent means none.
7704
- substitutedFamilies: z10.array(z10.string()).optional()
8123
+ substitutedFamilies: z11.array(z11.string()).optional()
7705
8124
  }),
7706
8125
  /** Coverage denominator (Q2 honesty): recorded vs full-lattice size
7707
8126
  * when the lattice is known (null when the kit exposes no lattice). */
7708
- coverage: z10.object({
7709
- recordedConfigs: z10.number().int().nonnegative(),
7710
- latticeConfigs: z10.number().int().positive().nullable()
8127
+ coverage: z11.object({
8128
+ recordedConfigs: z11.number().int().nonnegative(),
8129
+ latticeConfigs: z11.number().int().positive().nullable()
7711
8130
  }),
7712
- generatedAt: z10.string(),
7713
- spentUsd: z10.number().optional()
8131
+ generatedAt: z11.string(),
8132
+ spentUsd: z11.number().optional()
7714
8133
  });
7715
- BundleComponentJsonSchema = z10.object({
7716
- bundleVersion: z10.literal(BUNDLE_VERSION),
7717
- name: z10.string().min(1),
8134
+ BundleComponentJsonSchema = z11.object({
8135
+ bundleVersion: z11.literal(BUNDLE_VERSION),
8136
+ name: z11.string().min(1),
7718
8137
  /** Entry module file name (e.g. "Button.tsx"). */
7719
- entry: z10.string().min(1),
7720
- props: z10.array(PinnedPropSchema),
8138
+ entry: z11.string().min(1),
8139
+ props: z11.array(PinnedPropSchema),
7721
8140
  /** data-tendril-state tokens the bundle supports (A1.3 forcing hook). */
7722
- forcedStates: z10.array(z10.string()),
8141
+ forcedStates: z11.array(z11.string()),
7723
8142
  propAdapter: PropAdapterSchema,
7724
- requiredFonts: z10.array(RequiredFontSchema),
8143
+ requiredFonts: z11.array(RequiredFontSchema),
7725
8144
  /** A1.2: mirrors the RECORDING's role manifest — verify takes the
7726
8145
  * graph from the recording, so a bundle declaring no parts dodges
7727
8146
  * nothing. Absent when the recording claims no roles. */
7728
8147
  composition: RolesSchema.optional(),
7729
- configStatuses: z10.array(ConfigClaimSchema),
7730
- behaviorClaims: z10.array(BehaviorClaimSchema).optional(),
8148
+ configStatuses: z11.array(ConfigClaimSchema),
8149
+ behaviorClaims: z11.array(BehaviorClaimSchema).optional(),
7731
8150
  provenance: BundleProvenanceSchema,
7732
- trustStatement: z10.string().min(1)
8151
+ trustStatement: z11.string().min(1)
7733
8152
  });
7734
8153
  MAX_BUNDLE_MANIFEST_BYTES = 1e6;
7735
8154
  MAX_BUNDLE_SOURCE_BYTES = 5e6;
@@ -7861,9 +8280,9 @@ __export(record_exports, {
7861
8280
  runRecordPlan: () => runRecordPlan,
7862
8281
  runRecordStatus: () => runRecordStatus
7863
8282
  });
7864
- import { existsSync as existsSync21, mkdtempSync as mkdtempSync2, readFileSync as readFileSync17, readdirSync as readdirSync6 } from "node:fs";
8283
+ import { existsSync as existsSync22, mkdtempSync as mkdtempSync2, readFileSync as readFileSync18, readdirSync as readdirSync8 } from "node:fs";
7865
8284
  import os6 from "node:os";
7866
- import path27 from "node:path";
8285
+ import path28 from "node:path";
7867
8286
  import { writeFileSync as writeFileSync9 } from "node:fs";
7868
8287
  function recordsInteractionState(reports) {
7869
8288
  const evident = (s) => stateTokens(s).some((t) => INTERACTION_EVIDENCE_VALUES.has(t));
@@ -7886,7 +8305,7 @@ function interactionDisclosure(component, reports) {
7886
8305
  };
7887
8306
  }
7888
8307
  function symbolsFromMetadataEnvelope(file, sourceFrame) {
7889
- const env = JSON.parse(readFileSync17(file, "utf8"));
8308
+ const env = JSON.parse(readFileSync18(file, "utf8"));
7890
8309
  const text = env.content.map((c) => c.text ?? "").join("\n");
7891
8310
  const symbols = [];
7892
8311
  const walk2 = (node, ancestor) => {
@@ -7944,7 +8363,7 @@ function runRecordPlan(opts) {
7944
8363
  if (rawFile !== void 0) {
7945
8364
  try {
7946
8365
  const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
7947
- const tmp = path27.join(mkdtempSync2(path27.join(os6.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
8366
+ const tmp = path28.join(mkdtempSync2(path28.join(os6.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
7948
8367
  writeFileSync9(tmp, JSON.stringify(envelope));
7949
8368
  metadataEntries.push({ file: tmp });
7950
8369
  } catch (err) {
@@ -7966,7 +8385,7 @@ function runRecordPlan(opts) {
7966
8385
  let metadataTruncated = false;
7967
8386
  for (const { file, frame } of metadataEntries) {
7968
8387
  try {
7969
- const parsed = symbolsFromMetadataEnvelope(path27.resolve(file), frame);
8388
+ const parsed = symbolsFromMetadataEnvelope(path28.resolve(file), frame);
7970
8389
  symbols.push(...parsed.symbols);
7971
8390
  if (parsed.truncated) metadataTruncated = true;
7972
8391
  } catch (err) {
@@ -8000,7 +8419,7 @@ function runRecordPlan(opts) {
8000
8419
  if (symbols.length === 0) {
8001
8420
  const leads = metadataEntries.flatMap(({ file }) => {
8002
8421
  try {
8003
- const env = JSON.parse(readFileSync17(path27.resolve(file), "utf8"));
8422
+ const env = JSON.parse(readFileSync18(path28.resolve(file), "utf8"));
8004
8423
  return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
8005
8424
  } catch {
8006
8425
  return [];
@@ -8089,7 +8508,7 @@ function runRecordPlan(opts) {
8089
8508
  // a CLI flag and the MCP surface has no parameter for it.
8090
8509
  decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
8091
8510
  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.",
8092
- userRuns: [`rm ${quoteArg(path27.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
8511
+ userRuns: [`rm ${quoteArg(path28.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
8093
8512
  },
8094
8513
  {
8095
8514
  id: "larger-allowance",
@@ -8246,7 +8665,7 @@ function runRecordNext(opts) {
8246
8665
  const progress = payload["progress"];
8247
8666
  process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
8248
8667
  \u2192 ${payload["note"]}
8249
- \u2192 then: ${tendrilCommand(`record ingest --set ${path27.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
8668
+ \u2192 then: ${tendrilCommand(`record ingest --set ${path28.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
8250
8669
  `);
8251
8670
  });
8252
8671
  }
@@ -8320,7 +8739,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
8320
8739
  const skipped = [];
8321
8740
  const failed = [];
8322
8741
  for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
8323
- if (existsSync21(path27.join(setDir, rep, name))) {
8742
+ if (existsSync22(path28.join(setDir, rep, name))) {
8324
8743
  skipped.push(name);
8325
8744
  continue;
8326
8745
  }
@@ -8342,16 +8761,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
8342
8761
  }
8343
8762
  function rawEnvelopeFromFile(file, parts) {
8344
8763
  if (parts) {
8345
- const blocks = JSON.parse(readFileSync17(path27.resolve(file), "utf8"));
8764
+ const blocks = JSON.parse(readFileSync18(path28.resolve(file), "utf8"));
8346
8765
  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");
8347
8766
  return { content: blocks.map((text) => ({ type: "text", text })) };
8348
8767
  }
8349
- return { content: [{ type: "text", text: readFileSync17(path27.resolve(file), "utf8") }] };
8768
+ return { content: [{ type: "text", text: readFileSync18(path28.resolve(file), "utf8") }] };
8350
8769
  }
8351
8770
  async function runRecordIngest(opts) {
8352
8771
  let payload;
8353
8772
  try {
8354
- payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync17(path27.resolve(opts.file), "utf8"));
8773
+ payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync18(path28.resolve(opts.file), "utf8"));
8355
8774
  } catch (err) {
8356
8775
  fail(opts, ExitCode.InputValidation, {
8357
8776
  error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
@@ -8363,7 +8782,7 @@ async function runRecordIngest(opts) {
8363
8782
  fail(opts, ExitCode.InputValidation, {
8364
8783
  error: "--raw is for text tool responses; screenshots are binary",
8365
8784
  code: "envelope-invalid",
8366
- remediation: `Use \`${tendrilCommand(`record fetch --set ${path27.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
8785
+ remediation: `Use \`${tendrilCommand(`record fetch --set ${path28.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
8367
8786
  });
8368
8787
  }
8369
8788
  if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
@@ -8383,7 +8802,7 @@ async function runRecordIngest(opts) {
8383
8802
  remediation: REINGEST_GUIDANCE
8384
8803
  });
8385
8804
  }
8386
- writeFileSync9(path27.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
8805
+ writeFileSync9(path28.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
8387
8806
  `);
8388
8807
  emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
8389
8808
  process.stdout.write("set-level get_variable_defs ingested\n");
@@ -8399,7 +8818,7 @@ async function runRecordIngest(opts) {
8399
8818
  if (assets !== void 0) {
8400
8819
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
8401
8820
  `);
8402
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path27.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
8821
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path28.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
8403
8822
  `);
8404
8823
  }
8405
8824
  });
@@ -8472,15 +8891,15 @@ async function runRecordIngestRep(opts) {
8472
8891
  if (assets !== void 0) {
8473
8892
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
8474
8893
  `);
8475
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path27.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
8894
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path28.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
8476
8895
  `);
8477
8896
  }
8478
8897
  });
8479
8898
  }
8480
8899
  function runRecordAsset(opts) {
8481
8900
  if (opts.dir !== void 0) {
8482
- const dir = path27.resolve(opts.dir);
8483
- const names = readdirSync6(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
8901
+ const dir = path28.resolve(opts.dir);
8902
+ const names = readdirSync8(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
8484
8903
  if (names.length === 0) {
8485
8904
  fail(opts, ExitCode.InputValidation, {
8486
8905
  error: `no asset-*.<ext> files found in ${dir}`,
@@ -8491,7 +8910,7 @@ function runRecordAsset(opts) {
8491
8910
  const ingested = [];
8492
8911
  try {
8493
8912
  for (const name of names) {
8494
- ingestAsset(opts.setDir, opts.rep, name, readFileSync17(path27.join(dir, name)));
8913
+ ingestAsset(opts.setDir, opts.rep, name, readFileSync18(path28.join(dir, name)));
8495
8914
  ingested.push(name);
8496
8915
  }
8497
8916
  } catch (err) {
@@ -8511,11 +8930,11 @@ function runRecordAsset(opts) {
8511
8930
  fail(opts, ExitCode.InputValidation, {
8512
8931
  error: "pass --name and --file for a single asset, or --dir for a batch",
8513
8932
  code: "asset-rejected",
8514
- remediation: tendrilCommand(`record asset --set ${path27.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
8933
+ remediation: tendrilCommand(`record asset --set ${path28.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
8515
8934
  });
8516
8935
  }
8517
8936
  try {
8518
- ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync17(path27.resolve(opts.file)));
8937
+ ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync18(path28.resolve(opts.file)));
8519
8938
  emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
8520
8939
  process.stdout.write(`ingested ${opts.rep}/${opts.name}
8521
8940
  `);
@@ -8562,7 +8981,7 @@ function narrowedRoles(derived, override) {
8562
8981
  function rolesFromFile(opts, file, derived) {
8563
8982
  let json;
8564
8983
  try {
8565
- json = JSON.parse(readFileSync17(path27.resolve(file), "utf8"));
8984
+ json = JSON.parse(readFileSync18(path28.resolve(file), "utf8"));
8566
8985
  } catch (err) {
8567
8986
  fail(opts, ExitCode.InputValidation, {
8568
8987
  error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -8600,11 +9019,11 @@ function rolesFromFile(opts, file, derived) {
8600
9019
  };
8601
9020
  }
8602
9021
  function runRecordFinish(opts) {
8603
- if (!existsSync21(path27.join(opts.setDir, "recording-set.json"))) {
9022
+ if (!existsSync22(path28.join(opts.setDir, "recording-set.json"))) {
8604
9023
  fail(opts, ExitCode.InputValidation, {
8605
9024
  error: `no recording-set.json in ${opts.setDir}`,
8606
9025
  code: "no-recording-set",
8607
- remediation: `Run \`${tendrilCommand(`record plan --set ${path27.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
9026
+ remediation: `Run \`${tendrilCommand(`record plan --set ${path28.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
8608
9027
  });
8609
9028
  }
8610
9029
  const { manifest, raw } = readManifestFile(opts.setDir);
@@ -8632,17 +9051,17 @@ function runRecordFinish(opts) {
8632
9051
  fail(opts, ExitCode.ConfirmationRequired, {
8633
9052
  error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
8634
9053
  code: "roles-confirmation-not-interactive",
8635
- remediation: `A human runs \`${tendrilCommand(`record finish --set ${path27.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
9054
+ remediation: `A human runs \`${tendrilCommand(`record finish --set ${path28.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
8636
9055
  });
8637
9056
  }
8638
9057
  const merged = { ...raw, roles };
8639
- const { issues } = validateRecordingSet(merged, (rel) => existsSync21(path27.join(opts.setDir, rel)));
9058
+ const { issues } = validateRecordingSet(merged, (rel) => existsSync22(path28.join(opts.setDir, rel)));
8640
9059
  const errors = issues.filter((i) => i.severity === "error");
8641
9060
  if (errors.length > 0) {
8642
9061
  fail(opts, ExitCode.InputValidation, {
8643
9062
  error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
8644
9063
  code: "recording-set-invalid",
8645
- remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path27.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
9064
+ remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path28.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
8646
9065
  });
8647
9066
  }
8648
9067
  for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
@@ -8876,9 +9295,9 @@ var init_engine_curated = __esm({
8876
9295
  });
8877
9296
 
8878
9297
  // packages/generate/src/loop.ts
8879
- import { existsSync as existsSync22, mkdirSync as mkdirSync6, readFileSync as readFileSync18, renameSync, writeFileSync as writeFileSync10 } from "node:fs";
8880
- import path28 from "node:path";
8881
- import { z as z11 } from "zod";
9298
+ import { existsSync as existsSync23, mkdirSync as mkdirSync6, readFileSync as readFileSync19, renameSync, writeFileSync as writeFileSync10 } from "node:fs";
9299
+ import path29 from "node:path";
9300
+ import { z as z12 } from "zod";
8882
9301
  function objective(scores, behaviors) {
8883
9302
  const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
8884
9303
  return [
@@ -8914,9 +9333,9 @@ ${absentLines.join("\n")}` : ""}
8914
9333
  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."}`;
8915
9334
  }
8916
9335
  function archivePriorRun(outDir) {
8917
- if (!existsSync22(path28.join(outDir, "run-log.json")) && !existsSync22(path28.join(outDir, "loop-state.json"))) return void 0;
9336
+ if (!existsSync23(path29.join(outDir, "run-log.json")) && !existsSync23(path29.join(outDir, "loop-state.json"))) return void 0;
8918
9337
  let n = 1;
8919
- while (existsSync22(`${outDir}-prev-${n}`)) n += 1;
9338
+ while (existsSync23(`${outDir}-prev-${n}`)) n += 1;
8920
9339
  renameSync(outDir, `${outDir}-prev-${n}`);
8921
9340
  return `${outDir}-prev-${n}`;
8922
9341
  }
@@ -8925,14 +9344,14 @@ async function runEngineLoop(opts) {
8925
9344
  const plateau = opts.plateau ?? 2;
8926
9345
  const progress = opts.onProgress ?? (() => {
8927
9346
  });
8928
- const statePath = path28.join(opts.outDir, "loop-state.json");
8929
- const resuming = opts.resume === true && existsSync22(statePath);
9347
+ const statePath = path29.join(opts.outDir, "loop-state.json");
9348
+ const resuming = opts.resume === true && existsSync23(statePath);
8930
9349
  if (!resuming) {
8931
9350
  const archived = archivePriorRun(opts.outDir);
8932
9351
  if (archived !== void 0) progress(`previous run archived to ${archived}`);
8933
9352
  }
8934
9353
  mkdirSync6(opts.outDir, { recursive: true });
8935
- const scratch = path28.join(opts.outDir, ".candidate");
9354
+ const scratch = path29.join(opts.outDir, ".candidate");
8936
9355
  let attempts = [];
8937
9356
  let log = [];
8938
9357
  let best;
@@ -8940,7 +9359,7 @@ async function runEngineLoop(opts) {
8940
9359
  let nonAccepted = 0;
8941
9360
  let stopReason = "max-iterations";
8942
9361
  if (resuming) {
8943
- const restored = LoopStateSchema.parse(JSON.parse(readFileSync18(statePath, "utf8")));
9362
+ const restored = LoopStateSchema.parse(JSON.parse(readFileSync19(statePath, "utf8")));
8944
9363
  attempts = restored.attempts;
8945
9364
  log = restored.iterations;
8946
9365
  spentUsd = restored.spentUsd;
@@ -8960,7 +9379,7 @@ async function runEngineLoop(opts) {
8960
9379
  };
8961
9380
  const writeCandidate = (files) => {
8962
9381
  mkdirSync6(scratch, { recursive: true });
8963
- for (const [name, content] of Object.entries(files)) writeFileSync10(path28.join(scratch, name), content);
9382
+ for (const [name, content] of Object.entries(files)) writeFileSync10(path29.join(scratch, name), content);
8964
9383
  };
8965
9384
  const scoreCandidate = async (candidate, iter, usd, modelMs) => {
8966
9385
  writeCandidate(candidate.files);
@@ -9018,8 +9437,8 @@ async function runEngineLoop(opts) {
9018
9437
  const usd = candidate.usage?.usd ?? 0;
9019
9438
  spentUsd += usd;
9020
9439
  if (candidate.raw !== void 0) {
9021
- mkdirSync6(path28.join(opts.outDir, "responses"), { recursive: true });
9022
- writeFileSync10(path28.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
9440
+ mkdirSync6(path29.join(opts.outDir, "responses"), { recursive: true });
9441
+ writeFileSync10(path29.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
9023
9442
  }
9024
9443
  if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
9025
9444
  const finish = candidate.usage?.finishReason ?? "?";
@@ -9045,10 +9464,10 @@ async function runEngineLoop(opts) {
9045
9464
  }
9046
9465
  }
9047
9466
  }
9048
- if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(path28.join(opts.outDir, name), content);
9467
+ if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(path29.join(opts.outDir, name), content);
9049
9468
  const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
9050
9469
  writeFileSync10(
9051
- path28.join(opts.outDir, "run-log.json"),
9470
+ path29.join(opts.outDir, "run-log.json"),
9052
9471
  `${JSON.stringify(
9053
9472
  {
9054
9473
  ...opts.meta,
@@ -9072,42 +9491,42 @@ var init_loop2 = __esm({
9072
9491
  "packages/generate/src/loop.ts"() {
9073
9492
  "use strict";
9074
9493
  better = (a, b) => a[0] !== b[0] ? a[0] > b[0] : a[1] !== b[1] ? a[1] > b[1] : a[2] > b[2];
9075
- RegionSchema = z11.object({ x0: z11.number(), y0: z11.number(), x1: z11.number(), y1: z11.number(), density: z11.number() });
9076
- ConfigScoreSchema = z11.object({
9077
- rep: z11.string(),
9078
- similarity: z11.number(),
9079
- inkRecall: z11.number(),
9080
- pass: z11.boolean(),
9081
- error: z11.string().optional(),
9494
+ RegionSchema = z12.object({ x0: z12.number(), y0: z12.number(), x1: z12.number(), y1: z12.number(), density: z12.number() });
9495
+ ConfigScoreSchema = z12.object({
9496
+ rep: z12.string(),
9497
+ similarity: z12.number(),
9498
+ inkRecall: z12.number(),
9499
+ pass: z12.boolean(),
9500
+ error: z12.string().optional(),
9082
9501
  region: RegionSchema.optional()
9083
9502
  });
9084
- BehaviorResultSchema = z11.object({ id: z11.string(), pass: z11.boolean(), detail: z11.string().optional() });
9085
- LoopStateSchema = z11.object({
9086
- version: z11.literal(1),
9087
- spentUsd: z11.number(),
9088
- attempts: z11.array(
9089
- z11.object({
9090
- candidate: z11.object({
9091
- files: z11.record(z11.string(), z11.string()),
9092
- raw: z11.string().optional(),
9093
- usage: z11.object({ usd: z11.number(), inTokens: z11.number(), outTokens: z11.number(), modelMs: z11.number(), finishReason: z11.string().optional() }).optional()
9503
+ BehaviorResultSchema = z12.object({ id: z12.string(), pass: z12.boolean(), detail: z12.string().optional() });
9504
+ LoopStateSchema = z12.object({
9505
+ version: z12.literal(1),
9506
+ spentUsd: z12.number(),
9507
+ attempts: z12.array(
9508
+ z12.object({
9509
+ candidate: z12.object({
9510
+ files: z12.record(z12.string(), z12.string()),
9511
+ raw: z12.string().optional(),
9512
+ usage: z12.object({ usd: z12.number(), inTokens: z12.number(), outTokens: z12.number(), modelMs: z12.number(), finishReason: z12.string().optional() }).optional()
9094
9513
  }),
9095
- rulerScore: z11.object({ passCount: z11.number(), floor: z11.number(), mean: z11.number() }),
9096
- accepted: z11.boolean(),
9097
- feedback: z11.string()
9514
+ rulerScore: z12.object({ passCount: z12.number(), floor: z12.number(), mean: z12.number() }),
9515
+ accepted: z12.boolean(),
9516
+ feedback: z12.string()
9098
9517
  })
9099
9518
  ),
9100
- iterations: z11.array(
9101
- z11.object({
9102
- iter: z11.number(),
9103
- usd: z11.number(),
9104
- modelMs: z11.number().optional(),
9105
- scoreMs: z11.number().optional(),
9106
- objective: z11.tuple([z11.number(), z11.number(), z11.number()]),
9107
- accepted: z11.boolean(),
9108
- scores: z11.array(ConfigScoreSchema),
9109
- behaviors: z11.array(BehaviorResultSchema).optional(),
9110
- parseError: z11.string().optional()
9519
+ iterations: z12.array(
9520
+ z12.object({
9521
+ iter: z12.number(),
9522
+ usd: z12.number(),
9523
+ modelMs: z12.number().optional(),
9524
+ scoreMs: z12.number().optional(),
9525
+ objective: z12.tuple([z12.number(), z12.number(), z12.number()]),
9526
+ accepted: z12.boolean(),
9527
+ scores: z12.array(ConfigScoreSchema),
9528
+ behaviors: z12.array(BehaviorResultSchema).optional(),
9529
+ parseError: z12.string().optional()
9111
9530
  })
9112
9531
  )
9113
9532
  });
@@ -9115,8 +9534,8 @@ var init_loop2 = __esm({
9115
9534
  });
9116
9535
 
9117
9536
  // packages/generate/src/brief.ts
9118
- import { existsSync as existsSync23, readFileSync as readFileSync19 } from "node:fs";
9119
- import path29 from "node:path";
9537
+ import { existsSync as existsSync24, readFileSync as readFileSync20 } from "node:fs";
9538
+ import path30 from "node:path";
9120
9539
  function singleAxes2(name) {
9121
9540
  const parsed = parseVariantAxes(name);
9122
9541
  if (parsed === void 0) return void 0;
@@ -9357,21 +9776,21 @@ function authorBehaviors(api, extras = {}) {
9357
9776
  return { behaviors, prelude: { controls: interactive ? ["> *"] : [], textInputs: [] }, disclosures };
9358
9777
  }
9359
9778
  function envelopeText(file) {
9360
- return envelopeFirstTextPart(JSON.parse(readFileSync19(file, "utf8")));
9779
+ return envelopeFirstTextPart(JSON.parse(readFileSync20(file, "utf8")));
9361
9780
  }
9362
9781
  function metadataText(file) {
9363
- return envelopeTextContent(JSON.parse(readFileSync19(file, "utf8")));
9782
+ return envelopeTextContent(JSON.parse(readFileSync20(file, "utf8")));
9364
9783
  }
9365
9784
  function dismissEvidence(setDir, repSlugs) {
9366
9785
  for (const slug of repSlugs) {
9367
- const f = path29.join(setDir, slug, "get_design_context.json");
9368
- if (!existsSync23(f)) continue;
9786
+ const f = path30.join(setDir, slug, "get_design_context.json");
9787
+ if (!existsSync24(f)) continue;
9369
9788
  const text = envelopeText(f);
9370
9789
  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);
9371
9790
  if (propHit !== null) return `emission prop "${propHit[1]}"`;
9372
9791
  for (const m of text.matchAll(/data-name="([^"]+)"/g)) {
9373
- const norm = m[1].toLowerCase().replace(/[^a-z0-9]/g, "");
9374
- if (DISMISS_NAMES.has(norm)) return `layer ${JSON.stringify(m[1])}`;
9792
+ const norm2 = m[1].toLowerCase().replace(/[^a-z0-9]/g, "");
9793
+ if (DISMISS_NAMES.has(norm2)) return `layer ${JSON.stringify(m[1])}`;
9375
9794
  }
9376
9795
  }
9377
9796
  return void 0;
@@ -9409,13 +9828,13 @@ function recordedFontNeeds(setDir, opts = {}) {
9409
9828
  }
9410
9829
  };
9411
9830
  const manifest = loadManifest(setDir);
9412
- const setDefs = path29.join(setDir, "get_variable_defs.json");
9413
- if (existsSync23(setDefs)) fromDefs(envelopeText(setDefs));
9831
+ const setDefs = path30.join(setDir, "get_variable_defs.json");
9832
+ if (existsSync24(setDefs)) fromDefs(envelopeText(setDefs));
9414
9833
  for (const rep of manifest.reps) {
9415
- const ctx = path29.join(setDir, rep.slug, "get_design_context.json");
9416
- if (existsSync23(ctx)) fromEmission(envelopeText(ctx));
9417
- const defs = path29.join(setDir, rep.slug, "get_variable_defs.json");
9418
- if (existsSync23(defs)) fromDefs(envelopeText(defs));
9834
+ const ctx = path30.join(setDir, rep.slug, "get_design_context.json");
9835
+ if (existsSync24(ctx)) fromEmission(envelopeText(ctx));
9836
+ const defs = path30.join(setDir, rep.slug, "get_variable_defs.json");
9837
+ if (existsSync24(defs)) fromDefs(envelopeText(defs));
9419
9838
  }
9420
9839
  return [...byFamily.entries()].map(([family, paired]) => {
9421
9840
  const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
@@ -9426,10 +9845,10 @@ function symbolFontGlyphCount(setDir, reps) {
9426
9845
  const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
9427
9846
  const glyphs = /* @__PURE__ */ new Set();
9428
9847
  for (const rep of reps) {
9429
- const file = path29.join(setDir, rep, "get_metadata.json");
9430
- if (!existsSync23(file)) continue;
9848
+ const file = path30.join(setDir, rep, "get_metadata.json");
9849
+ if (!existsSync24(file)) continue;
9431
9850
  try {
9432
- const text = JSON.parse(readFileSync19(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
9851
+ const text = JSON.parse(readFileSync20(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
9433
9852
  for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
9434
9853
  const name = decodeXmlEntities(m[1]).replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)));
9435
9854
  if (PUA.test(name)) glyphs.add(name);
@@ -9455,8 +9874,8 @@ function recordedTextSlots(setDir, repSlugs) {
9455
9874
  const propRep = [];
9456
9875
  const perRep = [];
9457
9876
  for (const slug of repSlugs) {
9458
- const f = path29.join(setDir, slug, "get_design_context.json");
9459
- if (!existsSync23(f)) continue;
9877
+ const f = path30.join(setDir, slug, "get_design_context.json");
9878
+ if (!existsSync24(f)) continue;
9460
9879
  const code = envelopeText(f);
9461
9880
  const props = /* @__PURE__ */ new Map();
9462
9881
  for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
@@ -9481,8 +9900,8 @@ function recordedTextSlots(setDir, repSlugs) {
9481
9900
  }
9482
9901
  const axisValuesBySlug = /* @__PURE__ */ new Map();
9483
9902
  for (const slug of repSlugs) {
9484
- const metaFile = path29.join(setDir, slug, "get_metadata.json");
9485
- if (!existsSync23(metaFile)) continue;
9903
+ const metaFile = path30.join(setDir, slug, "get_metadata.json");
9904
+ if (!existsSync24(metaFile)) continue;
9486
9905
  const name = symbolName(metadataText(metaFile));
9487
9906
  if (name === void 0) continue;
9488
9907
  const values = /* @__PURE__ */ new Set();
@@ -9581,8 +10000,8 @@ function authorTaskFromSet(setDir, opts = {}) {
9581
10000
  const poses = [];
9582
10001
  const missing = [];
9583
10002
  for (const rep of manifest.reps) {
9584
- const metaFile = path29.join(setDir, rep.slug, "get_metadata.json");
9585
- if (!existsSync23(metaFile)) {
10003
+ const metaFile = path30.join(setDir, rep.slug, "get_metadata.json");
10004
+ if (!existsSync24(metaFile)) {
9586
10005
  missing.push(rep.slug);
9587
10006
  continue;
9588
10007
  }
@@ -9596,8 +10015,8 @@ function authorTaskFromSet(setDir, opts = {}) {
9596
10015
  if (missing.length > 0) {
9597
10016
  throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
9598
10017
  }
9599
- const setMeta = path29.join(setDir, "get_metadata.json");
9600
- const latticeNames = manifest.latticeNames ?? (existsSync23(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
10018
+ const setMeta = path30.join(setDir, "get_metadata.json");
10019
+ const latticeNames = manifest.latticeNames ?? (existsSync24(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
9601
10020
  const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
9602
10021
  const recordedFonts = recordedFontFamilies(setDir);
9603
10022
  const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
@@ -9780,10 +10199,10 @@ var init_brief = __esm({
9780
10199
  });
9781
10200
 
9782
10201
  // packages/generate/src/segments.ts
9783
- import { existsSync as existsSync24, readFileSync as readFileSync20, readdirSync as readdirSync7 } from "node:fs";
9784
- import path30 from "node:path";
10202
+ import { existsSync as existsSync25, readFileSync as readFileSync21, readdirSync as readdirSync9 } from "node:fs";
10203
+ import path31 from "node:path";
9785
10204
  function repText(set, rep, tool) {
9786
- const env = JSON.parse(readFileSync20(path30.join(set, rep, `${tool}.json`), "utf8"));
10205
+ const env = JSON.parse(readFileSync21(path31.join(set, rep, `${tool}.json`), "utf8"));
9787
10206
  return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
9788
10207
  }
9789
10208
  function stripFigmaInstructions(emission) {
@@ -9843,20 +10262,20 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
9843
10262
  }
9844
10263
  function buildSegments(task, mode = "fenced") {
9845
10264
  const SET = task.set;
9846
- let defsRecorded = existsSync24(path30.join(SET, "get_variable_defs.json"));
10265
+ let defsRecorded = existsSync25(path31.join(SET, "get_variable_defs.json"));
9847
10266
  let rawDefs = {};
9848
- if (existsSync24(path30.join(SET, "get_variable_defs.json"))) {
9849
- const text = envelopeFirstTextPart(JSON.parse(readFileSync20(path30.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
10267
+ if (existsSync25(path31.join(SET, "get_variable_defs.json"))) {
10268
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync21(path31.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
9850
10269
  try {
9851
10270
  rawDefs = JSON.parse(text);
9852
10271
  } catch {
9853
10272
  }
9854
10273
  } else {
9855
10274
  for (const cfg of task.configs) {
9856
- const f = path30.join(SET, cfg.rep, "get_variable_defs.json");
9857
- if (!existsSync24(f)) continue;
10275
+ const f = path31.join(SET, cfg.rep, "get_variable_defs.json");
10276
+ if (!existsSync25(f)) continue;
9858
10277
  defsRecorded = true;
9859
- const text = envelopeFirstTextPart(JSON.parse(readFileSync20(f, "utf8"))) || "{}";
10278
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync21(f, "utf8"))) || "{}";
9860
10279
  try {
9861
10280
  for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
9862
10281
  } catch {
@@ -9864,8 +10283,8 @@ function buildSegments(task, mode = "fenced") {
9864
10283
  }
9865
10284
  }
9866
10285
  const emissionTexts = task.configs.map((cfg) => {
9867
- const f = path30.join(SET, cfg.rep, "get_design_context.json");
9868
- return existsSync24(f) ? envelopeFirstTextPart(JSON.parse(readFileSync20(f, "utf8"))) : "";
10286
+ const f = path31.join(SET, cfg.rep, "get_design_context.json");
10287
+ return existsSync25(f) ? envelopeFirstTextPart(JSON.parse(readFileSync21(f, "utf8"))) : "";
9869
10288
  });
9870
10289
  const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
9871
10290
  const defs = JSON.stringify(map, null, 1);
@@ -9882,9 +10301,9 @@ TOKEN MAP NEVER RECORDED: this set holds no get_variable_defs envelope, so wheth
9882
10301
  for (const cfg of task.configs) {
9883
10302
  const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
9884
10303
  const emission = stripFigmaInstructions(repText(SET, cfg.rep, "get_design_context"));
9885
- const assets = readdirSync7(path30.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
10304
+ const assets = readdirSync9(path31.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
9886
10305
  \`\`\`svg
9887
- ${readFileSync20(path30.join(SET, cfg.rep, f), "utf8")}
10306
+ ${readFileSync21(path31.join(SET, cfg.rep, f), "utf8")}
9888
10307
  \`\`\``).join("\n");
9889
10308
  parts.push(`
9890
10309
  ## Config ${cfg.rep} \u2192 ${cfg.component} ${JSON.stringify(cfg.props)}
@@ -9909,7 +10328,7 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
9909
10328
  } else {
9910
10329
  parts.push(`
9911
10330
  ## Output format
9912
- Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path30.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.`);
10331
+ Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path31.basename(task.set)}-candidate/\` unless you were handed another path. Pass that same directory to \`tendril engine score\` every round and keep writing into it \u2014 the scorer stamps the bundle there, writes its evidence beside your files, and appends its score-history.jsonl lines there (one when a round starts, one when it scores); a fresh directory each round throws all of that away. Do not paste file contents into chat \u2014 the scorer reads the directory.`);
9913
10332
  }
9914
10333
  return parts.join("\n");
9915
10334
  }
@@ -9976,9 +10395,9 @@ var init_adapter = __esm({
9976
10395
  });
9977
10396
 
9978
10397
  // packages/generate/src/bundle-emit.ts
9979
- import { createHash as createHash4 } from "node:crypto";
9980
- import { copyFileSync, existsSync as existsSync25, mkdirSync as mkdirSync7, readFileSync as readFileSync21, readdirSync as readdirSync8, rmSync as rmSync3, writeFileSync as writeFileSync11 } from "node:fs";
9981
- import path31 from "node:path";
10398
+ import { createHash as createHash5 } from "node:crypto";
10399
+ import { copyFileSync, existsSync as existsSync26, mkdirSync as mkdirSync7, readFileSync as readFileSync22, readdirSync as readdirSync10, rmSync as rmSync3, writeFileSync as writeFileSync11 } from "node:fs";
10400
+ import path32 from "node:path";
9982
10401
  function pinFromConfigs(configs) {
9983
10402
  const domains = /* @__PURE__ */ new Map();
9984
10403
  const kinds = /* @__PURE__ */ new Map();
@@ -10047,9 +10466,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
10047
10466
  const notices = [];
10048
10467
  const licenseTexts = /* @__PURE__ */ new Map();
10049
10468
  for (const face of faces) {
10050
- const src = path31.join(cacheDir, path31.basename(face.file));
10051
- const target = `./fonts/${path31.basename(face.file)}`;
10052
- const format = FONT_FORMATS[path31.extname(face.file).toLowerCase()] ?? "truetype";
10469
+ const src = path32.join(cacheDir, path32.basename(face.file));
10470
+ const target = `./fonts/${path32.basename(face.file)}`;
10471
+ const format = FONT_FORMATS[path32.extname(face.file).toLowerCase()] ?? "truetype";
10053
10472
  const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
10054
10473
  const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
10055
10474
  const license = normalizeFontLicense(face.license);
@@ -10087,14 +10506,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
10087
10506
  `/* ${decl} */`
10088
10507
  );
10089
10508
  }
10090
- } else if (existsSync25(src) && createHash4("sha256").update(readFileSync21(src)).digest("hex") === face.sha256) {
10091
- mkdirSync7(path31.join(bundleDir, "fonts"), { recursive: true });
10092
- copyFileSync(src, path31.join(bundleDir, "fonts", path31.basename(face.file)));
10509
+ } else if (existsSync26(src) && createHash5("sha256").update(readFileSync22(src)).digest("hex") === face.sha256) {
10510
+ mkdirSync7(path32.join(bundleDir, "fonts"), { recursive: true });
10511
+ copyFileSync(src, path32.join(bundleDir, "fonts", path32.basename(face.file)));
10093
10512
  licenseTexts.set(terms.file, terms.text);
10094
10513
  const upstream = upstreamAttribution(face);
10095
10514
  notices.push(
10096
10515
  "",
10097
- `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path31.basename(face.file)}`,
10516
+ `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path32.basename(face.file)}`,
10098
10517
  ` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
10099
10518
  ` source: ${face.source}`,
10100
10519
  ` sha256: ${face.sha256}`,
@@ -10108,9 +10527,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
10108
10527
  }
10109
10528
  if (lines.length === 0) return null;
10110
10529
  if (notices.length > 0) {
10111
- const fontsDir = path31.join(bundleDir, "fonts");
10112
- for (const [file, text] of licenseTexts) writeFileSync11(path31.join(fontsDir, file), text);
10113
- writeFileSync11(path31.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
10530
+ const fontsDir = path32.join(bundleDir, "fonts");
10531
+ for (const [file, text] of licenseTexts) writeFileSync11(path32.join(fontsDir, file), text);
10532
+ writeFileSync11(path32.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
10114
10533
  `);
10115
10534
  header.push(
10116
10535
  "/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
@@ -10122,10 +10541,10 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
10122
10541
  `;
10123
10542
  }
10124
10543
  function countLatticeSymbols(setDir) {
10125
- const manifestFile = path31.join(setDir, "recording-set.json");
10126
- if (existsSync25(manifestFile)) {
10544
+ const manifestFile = path32.join(setDir, "recording-set.json");
10545
+ if (existsSync26(manifestFile)) {
10127
10546
  try {
10128
- const stored = JSON.parse(readFileSync21(manifestFile, "utf8"));
10547
+ const stored = JSON.parse(readFileSync22(manifestFile, "utf8"));
10129
10548
  if (stored.variantScope !== "component-set") return null;
10130
10549
  const lattice = stored.latticeNames;
10131
10550
  if (lattice !== void 0 && lattice.length > 0) return lattice.length;
@@ -10133,13 +10552,13 @@ function countLatticeSymbols(setDir) {
10133
10552
  }
10134
10553
  }
10135
10554
  const files = [
10136
- path31.join(setDir, "get_metadata.json"),
10137
- ...existsSync25(setDir) ? readdirSync8(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path31.join(setDir, f)) : []
10138
- ].filter((f) => existsSync25(f));
10555
+ path32.join(setDir, "get_metadata.json"),
10556
+ ...existsSync26(setDir) ? readdirSync10(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path32.join(setDir, f)) : []
10557
+ ].filter((f) => existsSync26(f));
10139
10558
  if (files.length === 0) return null;
10140
10559
  let count = 0;
10141
10560
  for (const f of files) {
10142
- const text = envelopeTextContent(JSON.parse(readFileSync21(f, "utf8")));
10561
+ const text = envelopeTextContent(JSON.parse(readFileSync22(f, "utf8")));
10143
10562
  count += [...text.matchAll(/name="([^"]*)"/g)].filter((m) => m[1].includes("=")).length;
10144
10563
  }
10145
10564
  return count > 0 ? count : null;
@@ -10147,23 +10566,23 @@ function countLatticeSymbols(setDir) {
10147
10566
  function recordingSetHash(setDir, configs) {
10148
10567
  const relPaths = [];
10149
10568
  for (const name of ["recording-set.json", "completeness-manifest.json", "get_variable_defs.json", "get_metadata.json"]) {
10150
- if (existsSync25(path31.join(setDir, name))) relPaths.push(name);
10569
+ if (existsSync26(path32.join(setDir, name))) relPaths.push(name);
10151
10570
  }
10152
10571
  for (const cfg of configs) {
10153
10572
  for (const f of ["get_design_context.json", "get_metadata.json", "get_screenshot.json", "get_variable_defs.json"]) {
10154
- if (existsSync25(path31.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
10573
+ if (existsSync26(path32.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
10155
10574
  }
10156
- if (existsSync25(path31.join(setDir, cfg.rep))) {
10157
- for (const asset of readdirSync8(path31.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
10575
+ if (existsSync26(path32.join(setDir, cfg.rep))) {
10576
+ for (const asset of readdirSync10(path32.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
10158
10577
  relPaths.push(`${cfg.rep}/${asset}`);
10159
10578
  }
10160
10579
  }
10161
10580
  }
10162
10581
  return hashRecordingSet(
10163
10582
  relPaths,
10164
- (p) => new Uint8Array(readFileSync21(path31.join(setDir, p))),
10583
+ (p) => new Uint8Array(readFileSync22(path32.join(setDir, p))),
10165
10584
  (chunks) => {
10166
- const h = createHash4("sha256");
10585
+ const h = createHash5("sha256");
10167
10586
  for (const c of chunks) h.update(c);
10168
10587
  return h.digest("hex");
10169
10588
  }
@@ -10182,11 +10601,11 @@ function emitBundleV1(opts) {
10182
10601
  const certified = statuses.filter((s) => s.status === "certified").length;
10183
10602
  const pass = statuses.filter((s) => s.status !== "fail").length;
10184
10603
  const lattice = countLatticeSymbols(opts.task.set);
10185
- const interaction = opts.behaviors.filter((b) => !b.id.startsWith("prelude:") && !b.id.startsWith("parity:"));
10604
+ const interaction = opts.behaviors.filter((b) => !b.id.startsWith("prelude:") && !b.id.startsWith("parity:") && !b.id.startsWith("composition:"));
10186
10605
  const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
10187
10606
  const parity = opts.behaviors.filter((b) => b.id.startsWith("parity:"));
10188
- const cssFiles = ["styles.css", "tokens.css"].map((f) => path31.join(opts.bundleDir, f)).filter((f) => existsSync25(f));
10189
- const families = cssFontFamilies(cssFiles.map((f) => readFileSync21(f, "utf8")).join("\n"));
10607
+ const cssFiles = ["styles.css", "tokens.css"].map((f) => path32.join(opts.bundleDir, f)).filter((f) => existsSync26(f));
10608
+ const families = cssFontFamilies(cssFiles.map((f) => readFileSync22(f, "utf8")).join("\n"));
10190
10609
  const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
10191
10610
  family: f.family,
10192
10611
  weight: f.weight,
@@ -10215,7 +10634,7 @@ function emitBundleV1(opts) {
10215
10634
  // resolvable via verify's --set override).
10216
10635
  path: (() => {
10217
10636
  const base = process.env["INIT_CWD"] ?? process.cwd();
10218
- const rel = path31.relative(base, opts.task.set);
10637
+ const rel = path32.relative(base, opts.task.set);
10219
10638
  return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
10220
10639
  })(),
10221
10640
  component: opts.componentName,
@@ -10239,21 +10658,21 @@ function emitBundleV1(opts) {
10239
10658
  })
10240
10659
  };
10241
10660
  const written = [];
10242
- const manifestPath2 = path31.join(opts.bundleDir, "component.json");
10661
+ const manifestPath2 = path32.join(opts.bundleDir, "component.json");
10243
10662
  writeFileSync11(manifestPath2, `${JSON.stringify(manifest, null, 2)}
10244
10663
  `);
10245
10664
  written.push(manifestPath2);
10246
- const stylesPath = path31.join(opts.bundleDir, "styles.css");
10247
- if (existsSync25(stylesPath)) {
10665
+ const stylesPath = path32.join(opts.bundleDir, "styles.css");
10666
+ if (existsSync26(stylesPath)) {
10248
10667
  const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
10249
- const current = readFileSync21(stylesPath, "utf8");
10668
+ const current = readFileSync22(stylesPath, "utf8");
10250
10669
  const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
10251
10670
  writeFileSync11(stylesPath, `${comment}
10252
10671
  ${stripped}`);
10253
10672
  written.push(stylesPath);
10254
10673
  }
10255
- const fontsCssPath = path31.join(opts.bundleDir, "fonts.css");
10256
- rmSync3(path31.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
10674
+ const fontsCssPath = path32.join(opts.bundleDir, "fonts.css");
10675
+ rmSync3(path32.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
10257
10676
  rmSync3(fontsCssPath, { force: true });
10258
10677
  const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
10259
10678
  if (fontsCss !== null) {
@@ -10681,6 +11100,262 @@ DEALINGS IN THE FONT SOFTWARE.
10681
11100
  }
10682
11101
  });
10683
11102
 
11103
+ // packages/generate/src/compose-pins.ts
11104
+ import { createHash as createHash6 } from "node:crypto";
11105
+ import { existsSync as existsSync27, readFileSync as readFileSync23, readdirSync as readdirSync11, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
11106
+ import path33 from "node:path";
11107
+ function bundleDirs(roots, depth = 4) {
11108
+ const found = [];
11109
+ const seen = /* @__PURE__ */ new Set();
11110
+ const walk2 = (dir, remaining) => {
11111
+ let key;
11112
+ try {
11113
+ key = realpathSync3(dir);
11114
+ } catch {
11115
+ key = path33.resolve(dir);
11116
+ }
11117
+ if (seen.has(key)) return;
11118
+ seen.add(key);
11119
+ if (existsSync27(path33.join(dir, "component.json"))) {
11120
+ found.push(key);
11121
+ return;
11122
+ }
11123
+ if (remaining === 0) return;
11124
+ let entries;
11125
+ try {
11126
+ entries = readdirSync11(dir);
11127
+ } catch {
11128
+ return;
11129
+ }
11130
+ for (const e of entries) {
11131
+ if (e === "node_modules" || e.startsWith(".")) continue;
11132
+ const full = path33.join(dir, e);
11133
+ try {
11134
+ if (statSync3(full).isDirectory()) walk2(full, remaining - 1);
11135
+ } catch {
11136
+ }
11137
+ }
11138
+ };
11139
+ for (const r of roots) walk2(path33.resolve(r), depth);
11140
+ return found;
11141
+ }
11142
+ function composedPins(hostSet, libraryRoots) {
11143
+ const { rows, malformed } = confirmedCompositionStatus(hostSet);
11144
+ const issues = [];
11145
+ if (malformed !== void 0) issues.push(`compositions extension partially unreadable (${malformed}) \u2014 the affected entries are not pinned`);
11146
+ const usable = rows.filter((r) => r.status === "supported" || r.status === "stale-supported");
11147
+ for (const r of rows) {
11148
+ if (!usable.includes(r)) issues.push(`confirmed pair ${r.key} is ${r.status} \u2014 not pinned (${r.detail})`);
11149
+ }
11150
+ if (usable.length === 0) return { pins: [], issues };
11151
+ const candidates = bundleDirs(libraryRoots);
11152
+ const pins = [];
11153
+ for (const row of usable) {
11154
+ const partnerRels = row.key.split("+");
11155
+ let pinned = false;
11156
+ const failures = [];
11157
+ for (const rel of partnerRels) {
11158
+ const partnerSet = path33.resolve(hostSet, rel);
11159
+ let partnerTask;
11160
+ let partnerManifest;
11161
+ try {
11162
+ partnerManifest = loadManifest(partnerSet);
11163
+ partnerTask = authorTaskFromSet(partnerSet).task;
11164
+ } catch (err) {
11165
+ failures.push(`${rel}: partner set does not author (${err instanceof Error ? err.message.split("\n")[0] : String(err)})`);
11166
+ continue;
11167
+ }
11168
+ const slugByVariant = new Map(partnerManifest.reps.map((rep) => [rep.nodeId, rep.slug]));
11169
+ const wantedPoses = /* @__PURE__ */ new Map();
11170
+ let poseMissing = false;
11171
+ for (const inst of row.instances) {
11172
+ const slug = slugByVariant.get(inst.poseVariantNodeId);
11173
+ const cfg = slug !== void 0 ? partnerTask.configs.find((c) => c.rep === slug) : void 0;
11174
+ if (slug === void 0 || cfg === void 0) {
11175
+ poseMissing = true;
11176
+ break;
11177
+ }
11178
+ wantedPoses.set(inst.poseVariantNodeId, slug);
11179
+ }
11180
+ if (poseMissing) {
11181
+ failures.push(`${rel}: does not record the confirmed pose`);
11182
+ continue;
11183
+ }
11184
+ const wantHash = recordingSetHash(partnerSet, partnerTask.configs);
11185
+ const matches = candidates.filter((dir) => {
11186
+ try {
11187
+ const parsed = readBundleManifest(readFileSync23(path33.join(dir, "component.json"), "utf8"));
11188
+ return parsed.manifest?.provenance.recordingSet.hash === wantHash;
11189
+ } catch {
11190
+ return false;
11191
+ }
11192
+ });
11193
+ if (matches.length === 0) {
11194
+ failures.push(
11195
+ `${rel}: no generated bundle joins this set's CURRENT recording hash (searched ${libraryRoots.length} root(s) to depth 4) \u2014 generate the partner first (a stale partner bundle does not join; regenerate it), or pass --library <dir> if the bundle lives elsewhere`
11196
+ );
11197
+ continue;
11198
+ }
11199
+ if (matches.length > 1) {
11200
+ failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) => path33.basename(m)).join(", ")}) \u2014 ambiguous; remove or point --library away from the duplicates`);
11201
+ continue;
11202
+ }
11203
+ const bundleDir = matches[0];
11204
+ let manifest;
11205
+ try {
11206
+ manifest = readBundleManifest(readFileSync23(path33.join(bundleDir, "component.json"), "utf8")).manifest;
11207
+ } catch {
11208
+ manifest = void 0;
11209
+ }
11210
+ if (manifest === void 0) {
11211
+ failures.push(`${rel}: partner bundle manifest at ${bundleDir} became unreadable`);
11212
+ continue;
11213
+ }
11214
+ if (!safeSegment(manifest.name) || !safeSegment(manifest.entry)) {
11215
+ failures.push(
11216
+ `${rel}: partner bundle at ${bundleDir} declares an unsafe name/entry (${JSON.stringify(manifest.name)} / ${JSON.stringify(manifest.entry)}) \u2014 refused; both must be a single plain path segment`
11217
+ );
11218
+ continue;
11219
+ }
11220
+ const moduleFiles = [];
11221
+ let fileIssue;
11222
+ for (const name of [manifest.entry, "styles.css", "tokens.css", "fonts.css"]) {
11223
+ const file = path33.join(bundleDir, name);
11224
+ if (!existsSync27(file)) {
11225
+ if (name === manifest.entry || name === "styles.css") {
11226
+ fileIssue = `${rel}: partner bundle is missing ${name}`;
11227
+ break;
11228
+ }
11229
+ continue;
11230
+ }
11231
+ let bytes;
11232
+ try {
11233
+ bytes = readFileSync23(file);
11234
+ } catch {
11235
+ fileIssue = `${rel}: partner file ${name} at ${bundleDir} is unreadable`;
11236
+ break;
11237
+ }
11238
+ if (bytes.byteLength > MAX_PINNED_FILE_BYTES) {
11239
+ fileIssue = `${rel}: partner file ${name} exceeds the pin size cap (${bytes.byteLength} bytes)`;
11240
+ break;
11241
+ }
11242
+ moduleFiles.push({ name, content: bytes.toString("utf8"), sha256: createHash6("sha256").update(bytes).digest("hex") });
11243
+ }
11244
+ if (fileIssue !== void 0) {
11245
+ failures.push(fileIssue);
11246
+ continue;
11247
+ }
11248
+ pins.push({
11249
+ pairKey: row.key,
11250
+ partnerName: manifest.name,
11251
+ partnerBundleDir: bundleDir,
11252
+ moduleFiles,
11253
+ entryModule: manifest.entry,
11254
+ entryComponent: manifest.entry.replace(/\.tsx?$/, ""),
11255
+ instances: row.instances.map((inst) => {
11256
+ const slug = slugByVariant.get(inst.poseVariantNodeId);
11257
+ const cfg = partnerTask.configs.find((c) => c.rep === slug);
11258
+ return { hostRep: inst.hostRep, instanceId: inst.instanceId, poseVariantNodeId: inst.poseVariantNodeId, partnerRep: slug, props: { ...cfg.props } };
11259
+ })
11260
+ });
11261
+ pinned = true;
11262
+ break;
11263
+ }
11264
+ if (!pinned) {
11265
+ issues.push(`confirmed pair ${row.key} could not be pinned: ${failures.join("; ") || "no partner recording authored"}`);
11266
+ }
11267
+ }
11268
+ const byDir = /* @__PURE__ */ new Map();
11269
+ for (const pin of pins) {
11270
+ const dir = composedModuleDir(pin.partnerName);
11271
+ byDir.set(dir, [...byDir.get(dir) ?? [], pin]);
11272
+ }
11273
+ const conflicted = /* @__PURE__ */ new Set();
11274
+ for (const [dir, group] of byDir) {
11275
+ if (group.length < 2) continue;
11276
+ const signature = (p) => p.moduleFiles.map((f) => `${f.name}:${f.sha256}`).sort().join(",");
11277
+ if (new Set(group.map(signature)).size > 1) {
11278
+ for (const p of group) conflicted.add(p);
11279
+ issues.push(
11280
+ `pairs ${group.map((p) => p.pairKey).join(" and ")} both prescribe ${dir}/ with DIFFERENT bytes \u2014 mutually unsatisfiable, none pinned; rename one partner component or point --library away from one of the bundles`
11281
+ );
11282
+ }
11283
+ }
11284
+ return { pins: pins.filter((p) => !conflicted.has(p)), issues };
11285
+ }
11286
+ function declaredImports(source) {
11287
+ const stripped = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
11288
+ const specs = [];
11289
+ const patterns = [
11290
+ /(?:^|[^.\w])import\s[^"'`;]*?from\s*["']([^"']+)["']/g,
11291
+ /(?:^|[^.\w])export\s[^"'`;]*?from\s*["']([^"']+)["']/g,
11292
+ /(?:^|[^.\w])import\s*\(\s*["']([^"']+)["']\s*\)/g,
11293
+ /(?:^|[^.\w])require\s*\(\s*["']([^"']+)["']\s*\)/g
11294
+ ];
11295
+ for (const re of patterns) {
11296
+ for (const m of stripped.matchAll(re)) specs.push(m[1]);
11297
+ }
11298
+ return specs;
11299
+ }
11300
+ function composedChecks(candidateDir, hostEntry, pins) {
11301
+ const checks = [];
11302
+ let entrySource = "";
11303
+ try {
11304
+ entrySource = readFileSync23(path33.join(candidateDir, hostEntry), "utf8");
11305
+ } catch {
11306
+ }
11307
+ const candidateRoot = path33.resolve(candidateDir);
11308
+ for (const pin of pins) {
11309
+ const dir = composedModuleDir(pin.partnerName);
11310
+ const resolvedDir = path33.resolve(candidateDir, dir);
11311
+ if (!resolvedDir.startsWith(candidateRoot + path33.sep)) {
11312
+ checks.push({ id: `composition:${pin.pairKey}:module-verbatim`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
11313
+ checks.push({ id: `composition:${pin.pairKey}:imported`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
11314
+ continue;
11315
+ }
11316
+ const wrong = [];
11317
+ for (const f of pin.moduleFiles) {
11318
+ if (!safeSegment(f.name)) {
11319
+ wrong.push(`${f.name} refused (not a plain path segment)`);
11320
+ continue;
11321
+ }
11322
+ const target = path33.join(candidateDir, dir, f.name);
11323
+ if (!existsSync27(target)) {
11324
+ wrong.push(`${f.name} missing`);
11325
+ continue;
11326
+ }
11327
+ const sha = createHash6("sha256").update(readFileSync23(target)).digest("hex");
11328
+ if (sha !== f.sha256) wrong.push(`${f.name} differs from the pinned partner bytes`);
11329
+ }
11330
+ checks.push({
11331
+ id: `composition:${pin.pairKey}:module-verbatim`,
11332
+ pass: wrong.length === 0,
11333
+ ...wrong.length > 0 ? { detail: `pinned partner module not present verbatim at ${dir}/: ${wrong.join(", ")}` } : {}
11334
+ });
11335
+ const specifier = `./${dir}/${pin.entryComponent}`;
11336
+ const imported = declaredImports(entrySource).includes(specifier);
11337
+ checks.push({
11338
+ id: `composition:${pin.pairKey}:imported`,
11339
+ pass: imported,
11340
+ ...imported ? {} : { detail: `host entry has no import declaration for the pinned module (expected \`from "${specifier}"\`)` }
11341
+ });
11342
+ }
11343
+ return checks;
11344
+ }
11345
+ var composedModuleDir, safeSegment, MAX_PINNED_FILE_BYTES;
11346
+ var init_compose_pins = __esm({
11347
+ "packages/generate/src/compose-pins.ts"() {
11348
+ "use strict";
11349
+ init_src();
11350
+ init_src6();
11351
+ init_brief();
11352
+ init_bundle_emit();
11353
+ composedModuleDir = (partnerName) => path33.posix.join("composed", partnerName);
11354
+ safeSegment = (s) => /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(s) && !s.includes("..");
11355
+ MAX_PINNED_FILE_BYTES = 1024 * 1024;
11356
+ }
11357
+ });
11358
+
10684
11359
  // packages/generate/src/index.ts
10685
11360
  var init_src7 = __esm({
10686
11361
  "packages/generate/src/index.ts"() {
@@ -10694,13 +11369,14 @@ var init_src7 = __esm({
10694
11369
  init_consent();
10695
11370
  init_adapter();
10696
11371
  init_bundle_emit();
11372
+ init_compose_pins();
10697
11373
  }
10698
11374
  });
10699
11375
 
10700
11376
  // packages/cli/src/font-guidance.ts
10701
- import path32 from "node:path";
11377
+ import path34 from "node:path";
10702
11378
  function fontsUnprovenRemediation(setDir) {
10703
- const set = setDir === void 0 ? void 0 : path32.resolve(setDir);
11379
+ const set = setDir === void 0 ? void 0 : path34.resolve(setDir);
10704
11380
  if (set !== void 0) {
10705
11381
  try {
10706
11382
  const needs = recordedFontNeeds(set);
@@ -10775,8 +11451,8 @@ __export(fonts_exports, {
10775
11451
  runFontsResolveSet: () => runFontsResolveSet,
10776
11452
  runFontsStatus: () => runFontsStatus
10777
11453
  });
10778
- import { existsSync as existsSync26, readFileSync as readFileSync22 } from "node:fs";
10779
- import path33 from "node:path";
11454
+ import { existsSync as existsSync28, readFileSync as readFileSync24 } from "node:fs";
11455
+ import path35 from "node:path";
10780
11456
  async function runFontsResolve(opts) {
10781
11457
  const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
10782
11458
  const queryRefusal = systemFaceRefusal(opts.family.trim());
@@ -10797,7 +11473,7 @@ async function runFontsResolve(opts) {
10797
11473
  }
10798
11474
  }
10799
11475
  async function runFontsResolveSet(opts) {
10800
- const setDir = path33.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
11476
+ const setDir = path35.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
10801
11477
  let needs = [];
10802
11478
  try {
10803
11479
  needs = recordedFontNeeds(setDir);
@@ -10880,16 +11556,16 @@ async function runFontsResolveSet(opts) {
10880
11556
  }
10881
11557
  }
10882
11558
  function runFontsStatus(opts) {
10883
- const manifestPath2 = path33.join(opts.cacheDir, "manifest.json");
10884
- if (!existsSync26(manifestPath2)) {
11559
+ const manifestPath2 = path35.join(opts.cacheDir, "manifest.json");
11560
+ if (!existsSync28(manifestPath2)) {
10885
11561
  fail(opts, ExitCode.FontsUnproven, {
10886
11562
  error: `no font cache at ${opts.cacheDir}`,
10887
11563
  code: "fonts-unresolved",
10888
11564
  remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
10889
11565
  });
10890
11566
  }
10891
- const faces = JSON.parse(readFileSync22(manifestPath2, "utf8"));
10892
- const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path33.resolve(opts.lock), opts.cacheDir) : null;
11567
+ const faces = JSON.parse(readFileSync24(manifestPath2, "utf8"));
11568
+ const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path35.resolve(opts.lock), opts.cacheDir) : null;
10893
11569
  emitData(opts, { faces, lockVerdicts }, () => {
10894
11570
  for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
10895
11571
  `);
@@ -10933,13 +11609,13 @@ function familyMismatch(family, declared) {
10933
11609
  }
10934
11610
  function runFontsAdd(opts) {
10935
11611
  if (opts.set !== void 0) {
10936
- const declared = taskFontFamilies(path33.resolve(opts.set)) ?? [];
11612
+ const declared = taskFontFamilies(path35.resolve(opts.set)) ?? [];
10937
11613
  const mismatch = familyMismatch(opts.family, declared);
10938
11614
  if (mismatch !== void 0) {
10939
11615
  fail(opts, ExitCode.InputValidation, {
10940
11616
  error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. Adding it under this name would cache a face the mount never matches, and scoring would keep refusing for the family that is still missing.`,
10941
11617
  code: "font-family-not-declared",
10942
- remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path33.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
11618
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path35.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
10943
11619
  });
10944
11620
  }
10945
11621
  } else {
@@ -10998,13 +11674,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
10998
11674
  }
10999
11675
  function runFontsAddSystem(opts) {
11000
11676
  if (opts.set !== void 0) {
11001
- const declared = taskFontFamilies(path33.resolve(opts.set)) ?? [];
11677
+ const declared = taskFontFamilies(path35.resolve(opts.set)) ?? [];
11002
11678
  const mismatch = familyMismatch(opts.family, declared);
11003
11679
  if (mismatch !== void 0) {
11004
11680
  fail(opts, ExitCode.InputValidation, {
11005
11681
  error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. A face cached under a name the mount never matches leaves scoring refusing for the family that is still missing.`,
11006
11682
  code: "font-family-not-declared",
11007
- remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path33.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
11683
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path35.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
11008
11684
  });
11009
11685
  }
11010
11686
  } else {
@@ -11056,10 +11732,13 @@ var init_fonts = __esm({
11056
11732
  // packages/cli/src/commands/verify.ts
11057
11733
  var verify_exports = {};
11058
11734
  __export(verify_exports, {
11735
+ COMPOSE_ON_GENERATE_ARMED: () => COMPOSE_ON_GENERATE_ARMED,
11059
11736
  NO_ROLE_MANIFEST: () => NO_ROLE_MANIFEST,
11060
11737
  ROLES_NOT_RESOLVED: () => ROLES_NOT_RESOLVED,
11061
11738
  checkSummarySegments: () => checkSummarySegments,
11062
11739
  compositionReport: () => compositionReport,
11740
+ crossCompositionDemotions: () => crossCompositionDemotions,
11741
+ crossCompositionLines: () => crossCompositionLines,
11063
11742
  eyeCheck: () => eyeCheck,
11064
11743
  failureTally: () => failureTally,
11065
11744
  foldConfigStatus: () => foldConfigStatus,
@@ -11071,15 +11750,15 @@ __export(verify_exports, {
11071
11750
  resolveComposition: () => resolveComposition,
11072
11751
  runVerify: () => runVerify
11073
11752
  });
11074
- import { existsSync as existsSync27, readFileSync as readFileSync23 } from "node:fs";
11075
- import path34 from "node:path";
11753
+ import { existsSync as existsSync29, readFileSync as readFileSync25 } from "node:fs";
11754
+ import path36 from "node:path";
11076
11755
  function interactionCoverage(behaviors) {
11077
11756
  const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
11078
- const interaction = behaviors.filter((b) => !b.id.startsWith("prelude:") && !b.id.startsWith("parity:"));
11757
+ const interaction = behaviors.filter((b) => !b.id.startsWith("prelude:") && !b.id.startsWith("parity:") && !b.id.startsWith("composition:"));
11079
11758
  return {
11080
11759
  interactionChecks: interaction.length,
11081
11760
  interactionPassed: interaction.filter((b) => b.pass).length,
11082
- preludeChecks: behaviors.length - interaction.length - parity.length,
11761
+ preludeChecks: behaviors.filter((b) => b.id.startsWith("prelude:")).length,
11083
11762
  parityChecks: parity.length,
11084
11763
  parityPassed: parity.filter((b) => b.pass).length,
11085
11764
  // Asymmetric on purpose (review finding F3): a parity PASS never
@@ -11179,7 +11858,61 @@ function checkSummarySegments(input) {
11179
11858
  const occ = occlusionReport(input.occlusion);
11180
11859
  const occlusion = "unavailable" in occ ? `occlusion not applicable (${NO_OVERLAY_DECLARED})` : `occlusion ${tally(input.occlusion)}`;
11181
11860
  const operability = "short" in input.operability ? `operability UNVERIFIED (${input.operability.short})` : `operability ${input.operability.passed}/${input.operability.checks} interaction`;
11182
- return ` \xB7 ${operability} \xB7 ${composition} \xB7 ${occlusion}`;
11861
+ const cross = input.crossComposition === void 0 || input.crossComposition.rows.length === 0 && input.crossComposition.malformed === void 0 ? "" : input.crossComposition.malformed !== void 0 && input.crossComposition.rows.length === 0 ? ` \xB7 cross-composition NOT CHECKED (entries REJECTED)` : (() => {
11862
+ const supported = input.crossComposition.rows.filter((r) => r.status === "supported" || r.status === "stale-supported").length;
11863
+ const composed = supported > 0 ? `, ${input.crossComposition.composed ?? 0} composed` : "";
11864
+ return ` \xB7 cross-composition ${supported}/${input.crossComposition.rows.length} supported${composed}${input.crossComposition.malformed !== void 0 ? " (some entries REJECTED)" : ""}`;
11865
+ })();
11866
+ return ` \xB7 ${operability} \xB7 ${composition} \xB7 ${occlusion}${cross}`;
11867
+ }
11868
+ function crossCompositionDemotions(rows, scoredReps, armed, composedPairs = /* @__PURE__ */ new Set()) {
11869
+ const demote = [];
11870
+ const orphanFailRows = [];
11871
+ for (const row of rows) {
11872
+ const failing = row.status === "partner-missing" || row.status === "partner-unreadable" || row.status === "unsupported" || row.status === "stale-unsupported";
11873
+ const arming = armed && (row.status === "supported" || row.status === "stale-supported") && !composedPairs.has(row.key);
11874
+ if (!failing && !arming) continue;
11875
+ const reps = arming ? [...new Set(row.instances.map((i) => i.hostRep))] : row.affectedReps;
11876
+ const reason = arming ? `confirmed composition NOT COMPOSED \u2014 the pinned partner module is not present verbatim with its import declared; re-run \`tendril engine brief\` for this set (the pins ride the payload) and regenerate (ADR-013, ${row.key})` : `confirmed composition ${row.status} \u2014 ${row.detail} (ADR-013)`;
11877
+ for (const rep of reps) {
11878
+ if (scoredReps.has(rep)) demote.push({ rep, reason });
11879
+ else orphanFailRows.push({ rep, error: `confirmed composition names rep "${rep}", which this set does not score (${row.status}, pair ${row.key}) \u2014 the claim cannot be cleared; repair or re-decide the composition` });
11880
+ }
11881
+ }
11882
+ return { demote, orphanFailRows };
11883
+ }
11884
+ function crossCompositionLines(input) {
11885
+ const lines = [];
11886
+ if (input.malformed !== void 0) {
11887
+ const plural = input.malformed.includes(";") ? "ies" : "y";
11888
+ lines.push(
11889
+ `UNAVAILABLE cross-composition entr${plural} REJECTED (${input.malformed}) \u2014 the backstop did NOT run over the rejected entr${plural}; repair the manifest and re-verify. Instrument failure, not a clean bill.`
11890
+ );
11891
+ }
11892
+ for (const row of input.rows) {
11893
+ const isSupported = row.status === "supported" || row.status === "stale-supported";
11894
+ const staleNote = row.status === "stale-supported" ? `; NOTE: ${row.detail}` : "";
11895
+ if (isSupported && input.composedPairs.has(row.key)) {
11896
+ lines.push(
11897
+ `COMPOSITION COMPOSED ${row.displayName} [${row.key}] \u2014 pinned partner module present verbatim, import declared${staleNote}. Rendered-mount stamping and region crops are the named next increment; whole-frame pixels remain the pixel evidence.`
11898
+ );
11899
+ } else if (isSupported && input.armed) {
11900
+ lines.push(
11901
+ `COMPOSITION FAIL ${row.displayName} [${row.key}] \u2014 confirmed but NOT COMPOSED (pinned module not present verbatim with its import declared)${staleNote} \u2014 the affected configs are demoted; re-run \`tendril engine brief\` for this set (the pins ride the payload) and regenerate`
11902
+ );
11903
+ } else if (isSupported) {
11904
+ lines.push(
11905
+ `COMPOSITION PENDING ${row.displayName} [${row.key}] \u2014 confirmed, not composed in this bundle${staleNote}; verdict unchanged while compose-on-generate is disarmed (ADR-013 \xA73; a PIN ISSUE line below carries the cause when pinning itself failed)`
11906
+ );
11907
+ } else {
11908
+ lines.push(`COMPOSITION FAIL ${row.displayName} [${row.key}] \u2014 ${row.detail} \u2014 the affected configs are demoted`);
11909
+ }
11910
+ }
11911
+ const restatesRow = /^confirmed pair .+ is (?:partner-missing|partner-unreadable|unsupported|stale-unsupported) — not pinned/;
11912
+ for (const issue of input.pinIssues.filter((i) => !restatesRow.test(i))) {
11913
+ lines.push(`COMPOSITION PIN ISSUE ${issue}`);
11914
+ }
11915
+ return lines;
11183
11916
  }
11184
11917
  function latticeCoverage(setManifest, scoredConfigs) {
11185
11918
  const lattice = setManifest.latticeNames?.length;
@@ -11224,7 +11957,7 @@ function compositionReport(input) {
11224
11957
  function eyeCheck(bundleDir) {
11225
11958
  return {
11226
11959
  command: tendrilCommand(`inspect "${bundleDir}"`),
11227
- sheetPath: path34.join(bundleDir, "verify-evidence", "inspect.html"),
11960
+ sheetPath: path36.join(bundleDir, "verify-evidence", "inspect.html"),
11228
11961
  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."
11229
11962
  };
11230
11963
  }
@@ -11236,7 +11969,7 @@ function taskFromManifest(opts, manifest, setDir) {
11236
11969
  const adapterSlugs = Object.keys(manifest.propAdapter);
11237
11970
  const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
11238
11971
  const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
11239
- const registry = Object.values(TASKS).find((t) => path34.resolve(t.set) === path34.resolve(setDir));
11972
+ const registry = Object.values(TASKS).find((t) => path36.resolve(t.set) === path36.resolve(setDir));
11240
11973
  const authored = (() => {
11241
11974
  if (registry !== void 0) return void 0;
11242
11975
  try {
@@ -11275,19 +12008,19 @@ function taskFromManifest(opts, manifest, setDir) {
11275
12008
  }
11276
12009
  async function runVerify(opts) {
11277
12010
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
11278
- const setOverride = opts.set !== void 0 ? path34.resolve(callerCwd, opts.set) : void 0;
11279
- opts = { ...opts, bundleDir: path34.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
11280
- if (!existsSync27(opts.bundleDir)) {
12011
+ const setOverride = opts.set !== void 0 ? path36.resolve(callerCwd, opts.set) : void 0;
12012
+ opts = { ...opts, bundleDir: path36.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
12013
+ if (!existsSync29(opts.bundleDir)) {
11281
12014
  fail(opts, ExitCode.InputValidation, {
11282
12015
  error: `bundle directory not found: ${opts.bundleDir}`,
11283
12016
  code: "bundle-missing",
11284
12017
  remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
11285
12018
  });
11286
12019
  }
11287
- const manifestPath2 = path34.join(opts.bundleDir, "component.json");
12020
+ const manifestPath2 = path36.join(opts.bundleDir, "component.json");
11288
12021
  let manifest;
11289
- if (existsSync27(manifestPath2)) {
11290
- const { manifest: parsed, issues } = readBundleManifest(readFileSync23(manifestPath2, "utf8"));
12022
+ if (existsSync29(manifestPath2)) {
12023
+ const { manifest: parsed, issues } = readBundleManifest(readFileSync25(manifestPath2, "utf8"));
11291
12024
  if (issues.length > 0) {
11292
12025
  fail(opts, ExitCode.InputValidation, {
11293
12026
  error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -11315,21 +12048,21 @@ async function runVerify(opts) {
11315
12048
  task = registry;
11316
12049
  } else if (manifest !== void 0) {
11317
12050
  const resolveSetDir = (p) => {
11318
- if (path34.isAbsolute(p)) return p;
11319
- const fromRepo = path34.resolve(REPO_ROOT, p);
11320
- if (existsSync27(fromRepo)) return fromRepo;
11321
- return path34.resolve(callerCwd, p);
12051
+ if (path36.isAbsolute(p)) return p;
12052
+ const fromRepo = path36.resolve(REPO_ROOT, p);
12053
+ if (existsSync29(fromRepo)) return fromRepo;
12054
+ return path36.resolve(callerCwd, p);
11322
12055
  };
11323
12056
  const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
11324
- if (!existsSync27(path34.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path34.resolve(t.set) === path34.resolve(setDir))) {
12057
+ if (!existsSync29(path36.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path36.resolve(t.set) === path36.resolve(setDir))) {
11325
12058
  fail(opts, ExitCode.RecordingIncomplete, {
11326
12059
  error: `recording set not found or unmanifested: ${setDir}`,
11327
12060
  code: "recording-set-missing",
11328
12061
  remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
11329
12062
  });
11330
12063
  }
11331
- const registry = Object.values(TASKS).find((t) => path34.resolve(t.set) === path34.resolve(setDir));
11332
- if (registry !== void 0 && !existsSync27(path34.join(setDir, "recording-set.json"))) {
12064
+ const registry = Object.values(TASKS).find((t) => path36.resolve(t.set) === path36.resolve(setDir));
12065
+ if (registry !== void 0 && !existsSync29(path36.join(setDir, "recording-set.json"))) {
11333
12066
  const recordedSlugs = registry.configs.map((c) => c.rep);
11334
12067
  const adapterSlugs = Object.keys(manifest.propAdapter);
11335
12068
  unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
@@ -11355,9 +12088,9 @@ async function runVerify(opts) {
11355
12088
  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`);
11356
12089
  }
11357
12090
  for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
11358
- const p = path34.join(opts.bundleDir, name);
11359
- if (!existsSync27(p)) continue;
11360
- const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync23(p)));
12091
+ const p = path36.join(opts.bundleDir, name);
12092
+ if (!existsSync29(p)) continue;
12093
+ const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync25(p)));
11361
12094
  if (issues.length > 0) {
11362
12095
  fail(opts, ExitCode.InputValidation, {
11363
12096
  error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -11395,7 +12128,7 @@ async function runVerify(opts) {
11395
12128
  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)`);
11396
12129
  }
11397
12130
  const missing = task.configs.filter(
11398
- (c) => !existsSync27(path34.join(task.set, c.rep, "get_screenshot.json")) || !existsSync27(path34.join(task.set, c.rep, "get_metadata.json"))
12131
+ (c) => !existsSync29(path36.join(task.set, c.rep, "get_screenshot.json")) || !existsSync29(path36.join(task.set, c.rep, "get_metadata.json"))
11399
12132
  );
11400
12133
  if (missing.length > 0) {
11401
12134
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -11405,16 +12138,26 @@ async function runVerify(opts) {
11405
12138
  });
11406
12139
  }
11407
12140
  const bar = BARS2[opts.bar];
11408
- const evidenceDir = path34.join(opts.bundleDir, "verify-evidence");
12141
+ const evidenceDir = path36.join(opts.bundleDir, "verify-evidence");
11409
12142
  const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
11410
12143
  const quality = await checkBundleQuality(opts.bundleDir, task.entry, { dir: task.set, reps: task.configs.map((c) => c.rep) });
11411
- const bundleCss = ["tokens.css", "styles.css"].map((f) => path34.join(opts.bundleDir, f)).filter((f) => existsSync27(f)).map((f) => readFileSync23(f, "utf8")).join("\n");
12144
+ const bundleCss = ["tokens.css", "styles.css"].map((f) => path36.join(opts.bundleDir, f)).filter((f) => existsSync29(f)).map((f) => readFileSync25(f, "utf8")).join("\n");
11412
12145
  const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss);
11413
12146
  const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs);
11414
12147
  const behaviors = [...await checkBehaviors(task, opts.bundleDir), ...parity];
11415
12148
  const structural = roles !== void 0 ? await checkStructuralComposition(task, opts.bundleDir, roles) : [];
11416
12149
  const regionsOut = roles !== void 0 ? interiorRegions(task.set, roles) : void 0;
11417
12150
  const crops = regionsOut !== void 0 && "regions" in regionsOut ? await checkCropComposition(task, opts.bundleDir, regionsOut.regions) : void 0;
12151
+ const crossComposition = confirmedCompositionStatus(task.set);
12152
+ if (crossComposition.malformed !== void 0) {
12153
+ warn(opts, `compositions extension REJECTED (${crossComposition.malformed}) \u2014 the cross-bundle backstop did NOT run over it; repair the manifest entry and re-verify. This is an instrument failure, not a clean bill.`);
12154
+ }
12155
+ const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
12156
+ const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path36.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
12157
+ const verifyComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
12158
+ const composedPairs = new Set(
12159
+ verifyPins.pins.filter((pin) => verifyComposedChecks.filter((c) => c.id.startsWith(`composition:${pin.pairKey}:`)).every((c) => c.pass)).map((pin) => pin.pairKey)
12160
+ );
11418
12161
  const demotions = /* @__PURE__ */ new Map();
11419
12162
  const demote = (rep, reason) => {
11420
12163
  demotions.set(rep, [...demotions.get(rep) ?? [], reason]);
@@ -11424,13 +12167,20 @@ async function runVerify(opts) {
11424
12167
  if (crops !== void 0) {
11425
12168
  for (const c of crops) if (!c.pass) demote(c.id.split(":")[1] ?? "", "composition crop failed (A1.4)");
11426
12169
  }
12170
+ const scoredReps = /* @__PURE__ */ new Set([...scores.map((s) => s.rep), ...unmapped]);
12171
+ const crossOutcome = crossCompositionDemotions(crossComposition.rows, scoredReps, COMPOSE_ON_GENERATE_ARMED, composedPairs);
12172
+ for (const d of crossOutcome.demote) demote(d.rep, d.reason);
11427
12173
  const folded = scores.map((s) => foldConfigStatus(s, demotions.get(s.rep), substitutedFamilies));
11428
12174
  const absentInkDemoted = scores.filter((_, i) => folded[i].absentInkDemoted).map((s) => s.rep);
11429
12175
  const statuses = [
11430
12176
  ...folded.map((f) => f.row),
11431
12177
  // ADR-010 §2 anti-gaming: recorded configs the adapter does not map
11432
12178
  // are FAILs, never silently absent.
11433
- ...unmapped.map((rep) => ({ rep, similarity: 0, inkRecall: 0, pass: false, status: "fail", error: "not mapped by the bundle's prop adapter" }))
12179
+ ...unmapped.map((rep) => ({ rep, similarity: 0, inkRecall: 0, pass: false, status: "fail", error: "not mapped by the bundle's prop adapter" })),
12180
+ // ADR-013 2c-i: a confirmed-composition claim naming a rep this
12181
+ // set does not score is a loud synthetic FAILURE, never a printed
12182
+ // contradiction over an unchanged verdict.
12183
+ ...crossOutcome.orphanFailRows.map((o) => ({ rep: o.rep, similarity: 0, inkRecall: 0, pass: false, status: "fail", error: o.error }))
11434
12184
  ];
11435
12185
  const behaviorFailures = behaviors.filter((b) => !b.pass);
11436
12186
  const structuralFailures = structural.filter((s) => !s.pass);
@@ -11497,6 +12247,19 @@ async function runVerify(opts) {
11497
12247
  // occlusionReport): the agent channel had no counterpart to the
11498
12248
  // human line's "occlusion not applicable (no overlay declared)".
11499
12249
  occlusionCheck: occlusionReport(occlusion),
12250
+ // ADR-013 2c: the confirmed-composition backstop rows (re-derived
12251
+ // from recordings, never trusted from the manifest), the composed
12252
+ // checks (pinned bytes verbatim + import), and the pin issues.
12253
+ ...crossComposition.rows.length > 0 || crossComposition.malformed !== void 0 ? {
12254
+ crossComposition: {
12255
+ rows: crossComposition.rows,
12256
+ checks: verifyComposedChecks,
12257
+ composedPairs: [...composedPairs],
12258
+ pinIssues: verifyPins.issues,
12259
+ ...crossComposition.malformed !== void 0 ? { malformed: crossComposition.malformed } : {},
12260
+ note: "COMPOSED means the pinned partner module is present verbatim with its import declared; while compose-on-generate is disarmed (ADR-013 \xA73, re-arms with rendered-mount stamping) an uncomposed supported pair is DISCLOSED, never demoted \u2014 and whole-frame pixels remain the pixel evidence for composed regions"
12261
+ }
12262
+ } : {},
11500
12263
  configs: statuses,
11501
12264
  behaviors,
11502
12265
  evidence: { dir: evidenceDir, files: "per config: <rep>-render.png, <rep>-ref.png, <rep>-diff.png" },
@@ -11543,7 +12306,7 @@ async function runVerify(opts) {
11543
12306
  process.stdout.write(
11544
12307
  `
11545
12308
  ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuses.length} \u2265 pass bar \xB7 behaviors ${behaviors.length - behaviorFailures.length}/${behaviors.length}${checkSummarySegments(
11546
- { availability, structural, crops, occlusion, operability }
12309
+ { availability, structural, crops, occlusion, operability, crossComposition: { rows: crossComposition.rows, ...crossComposition.malformed !== void 0 ? { malformed: crossComposition.malformed } : {}, composed: composedPairs.size } }
11547
12310
  )}
11548
12311
  `
11549
12312
  );
@@ -11603,9 +12366,19 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
11603
12366
  );
11604
12367
  }
11605
12368
  }
12369
+ for (const line of crossCompositionLines({
12370
+ rows: crossComposition.rows,
12371
+ composedPairs,
12372
+ armed: COMPOSE_ON_GENERATE_ARMED,
12373
+ pinIssues: verifyPins.issues,
12374
+ ...crossComposition.malformed !== void 0 ? { malformed: crossComposition.malformed } : {}
12375
+ })) {
12376
+ process.stdout.write(`${line}
12377
+ `);
12378
+ }
11606
12379
  process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
11607
12380
  `);
11608
- const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path34.join(opts.bundleDir, f)).filter((f) => existsSync27(f)).map((f) => readFileSync23(f, "utf8")).join("\n")));
12381
+ const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path36.join(opts.bundleDir, f)).filter((f) => existsSync29(f)).map((f) => readFileSync25(f, "utf8")).join("\n")));
11609
12382
  if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
11610
12383
  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)
11611
12384
  `);
@@ -11658,7 +12431,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
11658
12431
  process.exitCode = ExitCode.VerificationFailed;
11659
12432
  }
11660
12433
  }
11661
- var BARS2, NO_INTERACTIVE_POSES, NO_ROLE_MANIFEST, ROLES_NOT_RESOLVED, UNSTAMPED_ROLES, NO_OVERLAY_DECLARED;
12434
+ var BARS2, NO_INTERACTIVE_POSES, NO_ROLE_MANIFEST, ROLES_NOT_RESOLVED, UNSTAMPED_ROLES, COMPOSE_ON_GENERATE_ARMED, NO_OVERLAY_DECLARED;
11662
12435
  var init_verify = __esm({
11663
12436
  "packages/cli/src/commands/verify.ts"() {
11664
12437
  "use strict";
@@ -11685,6 +12458,7 @@ var init_verify = __esm({
11685
12458
  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"
11686
12459
  });
11687
12460
  UNSTAMPED_ROLES = "unstamped";
12461
+ COMPOSE_ON_GENERATE_ARMED = false;
11688
12462
  NO_OVERLAY_DECLARED = "no overlay declared";
11689
12463
  }
11690
12464
  });
@@ -11695,18 +12469,18 @@ __export(engine_exports, {
11695
12469
  runEngineBrief: () => runEngineBrief,
11696
12470
  runEngineScore: () => runEngineScore
11697
12471
  });
11698
- import { appendFileSync, existsSync as existsSync28, mkdirSync as mkdirSync8, readFileSync as readFileSync24, writeFileSync as writeFileSync12 } from "node:fs";
11699
- import path35 from "node:path";
12472
+ import { appendFileSync, existsSync as existsSync30, mkdirSync as mkdirSync8, readFileSync as readFileSync26, writeFileSync as writeFileSync12 } from "node:fs";
12473
+ import path37 from "node:path";
11700
12474
  function resolveEngineTask(opts, callerCwd) {
11701
- const asPath = path35.resolve(callerCwd, opts.taskOrSet);
11702
- const isSet = existsSync28(path35.join(asPath, "recording-set.json"));
12475
+ const asPath = path37.resolve(callerCwd, opts.taskOrSet);
12476
+ const isSet = existsSync30(path37.join(asPath, "recording-set.json"));
11703
12477
  const registry = TASKS[opts.taskOrSet];
11704
12478
  if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
11705
12479
  if (isSet) {
11706
12480
  try {
11707
12481
  const authored = authorTaskFromSet(asPath);
11708
12482
  for (const d of authored.disclosures) warn(opts, d);
11709
- return { task: authored.task, name: path35.basename(asPath), ref: asPath, disclosures: authored.disclosures, interactionEvidence: authored.api.interactionEvidence, apiPin: { props: authored.api.apiPin.props, forcedStates: authored.api.apiPin.forcedStates } };
12483
+ return { task: authored.task, name: path37.basename(asPath), ref: asPath, disclosures: authored.disclosures, interactionEvidence: authored.api.interactionEvidence, apiPin: { props: authored.api.apiPin.props, forcedStates: authored.api.apiPin.forcedStates } };
11710
12484
  } catch (err) {
11711
12485
  fail(opts, ExitCode.InputValidation, {
11712
12486
  error: `cannot author a task from ${asPath}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -11733,9 +12507,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
11733
12507
  const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock;
11734
12508
  const segments = buildSegments(task, "files");
11735
12509
  let notRecorded;
11736
- const manifestPath2 = path35.join(task.set, "recording-set.json");
11737
- if (existsSync28(manifestPath2)) {
11738
- notRecorded = JSON.parse(readFileSync24(manifestPath2, "utf8")).notRecorded;
12510
+ const manifestPath2 = path37.join(task.set, "recording-set.json");
12511
+ if (existsSync30(manifestPath2)) {
12512
+ notRecorded = JSON.parse(readFileSync26(manifestPath2, "utf8")).notRecorded;
11739
12513
  }
11740
12514
  const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
11741
12515
 
@@ -11743,7 +12517,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
11743
12517
  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.
11744
12518
  ${notRecorded}` : "";
11745
12519
  let fontProvisioning;
11746
- if (existsSync28(manifestPath2)) {
12520
+ if (existsSync30(manifestPath2)) {
11747
12521
  const missingFams = unprovisionedFamilies(task.set);
11748
12522
  const unprovided = unprovisionedFaces(task.set);
11749
12523
  const weightOnly = missingFams.length === 0;
@@ -11765,13 +12539,31 @@ ${notRecorded}` : "";
11765
12539
  };
11766
12540
  }
11767
12541
  }
11768
- const payload = `${brief}${unverified}
12542
+ const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path37.resolve(callerCwd, opts.library) : callerCwd]);
12543
+ const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
12544
+ const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
12545
+
12546
+ === COMPOSED REGIONS (ADR-013 \u2014 each pair below was CONFIRMED BY A HUMAN; the pinned modules are PRESCRIBED API) ===
12547
+ CANON AMENDMENT for this task: "no imports beyond react/react-dom" is amended to ADDITIONALLY allow importing exactly the pinned modules below \u2014 nothing else. Write every pinned file VERBATIM (byte-for-byte) at the given candidate path; any edit fails the composition checks. Render the pinned entry component for each listed region with exactly the listed props; do not re-implement those pixels. The pinned styles/tokens CSS is injected at scoring time alongside yours \u2014 do not copy its rules into your own stylesheets.
12548
+ ` + pinsResult.pins.map(
12549
+ (pin) => `
12550
+ PIN ${pin.partnerName} [pair ${pin.pairKey}] \u2014 import from "./${composedModuleDir(pin.partnerName)}/${pin.entryComponent}"
12551
+ ` + pin.instances.map((i) => ` region ${i.hostRep}/${i.instanceId}: <${pin.entryComponent} ${Object.entries(i.props).map(jsxProp).join(" ")} /> (pose ${i.partnerRep})`).join("\n") + pin.moduleFiles.map((f) => `
12552
+ --- write VERBATIM to ${composedModuleDir(pin.partnerName)}/${f.name} (sha256 ${f.sha256}) ---
12553
+ ${f.content}`).join("")
12554
+ ).join("\n") : `
12555
+
12556
+ === COMPOSED REGIONS (ADR-013 \u2014 confirmed pairs exist, but NONE could be pinned) ===
12557
+ `) + (pinsResult.issues.length > 0 ? `
12558
+ NOT PINNED (named, never silent \u2014 these confirmed pairs could not be pinned): implement those regions inline and state it in your report. Verify will DISCLOSE each such pair as confirmed-but-not-composed; where the cause below is fixable (e.g. the partner bundle is missing), fixing it and re-running brief is the better path:
12559
+ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
12560
+ const payload = `${brief}${unverified}${composedBlock}
11769
12561
 
11770
12562
  === TASK PAYLOAD (recorded truth, verbatim) ===
11771
12563
  ${segments}`;
11772
- const payloadFile = path35.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
11773
- const candidateDirSuggestion = path35.resolve(callerCwd, `tendril-out/${name}-candidate`);
11774
- mkdirSync8(path35.dirname(payloadFile), { recursive: true });
12564
+ const payloadFile = path37.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
12565
+ const candidateDirSuggestion = path37.resolve(callerCwd, `tendril-out/${name}-candidate`);
12566
+ mkdirSync8(path37.dirname(payloadFile), { recursive: true });
11775
12567
  writeFileSync12(payloadFile, payload);
11776
12568
  emitData(
11777
12569
  opts,
@@ -11784,6 +12576,10 @@ ${segments}`;
11784
12576
  ...opts.model !== void 0 ? { declaredModel: opts.model } : {},
11785
12577
  ...notRecorded !== void 0 && notRecorded !== "" ? { notRecorded } : {},
11786
12578
  ...fontProvisioning !== void 0 ? { fontProvisioning } : {},
12579
+ // ADR-013: composition is IN PLAY for this task — an orchestrator
12580
+ // reading only this JSON must be able to see it (the payload file
12581
+ // carries the pins themselves).
12582
+ ...pinsResult.pins.length > 0 || pinsResult.issues.length > 0 ? { composition: { pinnedPairs: pinsResult.pins.map((p) => p.pairKey), pinIssues: pinsResult.issues } } : {},
11787
12583
  modelSelection: {
11788
12584
  instruction: "The model declaration is MECHANICAL: `engine score` refuses to run without --model. ONLY ask when the answer can take effect \u2014 i.e. you can delegate implementation to an agent running the chosen model; if you cannot delegate in this session, skip the question, build as yourself, and declare your own model honestly (a question whose answer changes nothing wastes the user's trust \u2014 measured, second Windows run). When you do ask: BEFORE reading the payload, using the template below verbatim where your host renders option dialogs. The audience is non-technical: plain cost/quality language, no model knowledge assumed. In a non-interactive session pick the balanced tier, state the reason in your report, and declare it \u2014 a silent default is not possible.",
11789
12585
  questionTemplate: {
@@ -11806,8 +12602,11 @@ ${segments}`;
11806
12602
  // arguments, and a placeholder is the other half of the same
11807
12603
  // defect — a command the reader has to finish is one they can
11808
12604
  // finish wrongly.
12605
+ // --library rides along when brief ran with one: the score
12606
+ // command must search the same bundle roots the pins came
12607
+ // from, or the oracle and the brief describe different worlds.
11809
12608
  `Run \`${tendrilCommand(
11810
- `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"} --json`
12609
+ `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path37.resolve(callerCwd, opts.library))}` : ""} --json`
11811
12610
  )}\` \u2014 the CLI is the sole judge, and it refuses to score an undeclared model. If you wrote the bundle elsewhere, pass that directory instead.`,
11812
12611
  "Apply the returned feedback and re-score. Stop when all checks pass. Two consecutive non-improving scores mean INSPECT the verify-evidence diffs before deciding \u2014 stop only when inspection yields no fix hypothesis (a measured run was byte-identical twice, then went 27/27 after reading the diffs).",
11813
12612
  "Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
@@ -11822,8 +12621,8 @@ ${segments}`;
11822
12621
  );
11823
12622
  }
11824
12623
  function appendScoreHistory(candidateDir, entry) {
11825
- const file = path35.join(candidateDir, "score-history.jsonl");
11826
- const starts = existsSync28(file) ? readFileSync24(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
12624
+ const file = path37.join(candidateDir, "score-history.jsonl");
12625
+ const starts = existsSync30(file) ? readFileSync26(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
11827
12626
  const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
11828
12627
  appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
11829
12628
  `);
@@ -11831,9 +12630,9 @@ function appendScoreHistory(candidateDir, entry) {
11831
12630
  async function runEngineScore(opts) {
11832
12631
  requireEntitlement(opts);
11833
12632
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
11834
- const candidateDir = path35.resolve(callerCwd, opts.candidateDir);
12633
+ const candidateDir = path37.resolve(callerCwd, opts.candidateDir);
11835
12634
  const { task, name, apiPin, interactionEvidence } = resolveEngineTask(opts, callerCwd);
11836
- if (!existsSync28(candidateDir)) {
12635
+ if (!existsSync30(candidateDir)) {
11837
12636
  fail(opts, ExitCode.InputValidation, {
11838
12637
  error: `candidate directory not found: ${candidateDir}`,
11839
12638
  code: "candidate-missing",
@@ -11858,10 +12657,10 @@ async function runEngineScore(opts) {
11858
12657
  for (const g of missingWeights(task.set)) {
11859
12658
  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)`);
11860
12659
  }
11861
- if (opts.rebind !== true && existsSync28(path35.join(candidateDir, "component.json"))) {
12660
+ if (opts.rebind !== true && existsSync30(path37.join(candidateDir, "component.json"))) {
11862
12661
  const prior = (() => {
11863
12662
  try {
11864
- const read = readBundleManifest(readFileSync24(path35.join(candidateDir, "component.json"), "utf8"));
12663
+ const read = readBundleManifest(readFileSync26(path37.join(candidateDir, "component.json"), "utf8"));
11865
12664
  return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
11866
12665
  } catch {
11867
12666
  return { unreadable: true };
@@ -11883,10 +12682,12 @@ async function runEngineScore(opts) {
11883
12682
  }
11884
12683
  }
11885
12684
  const bar = BARS3[opts.bar];
11886
- const evidenceDir = path35.join(candidateDir, "verify-evidence");
12685
+ const evidenceDir = path37.join(candidateDir, "verify-evidence");
11887
12686
  const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
11888
12687
  const parity = await checkHoverParity(task, candidateDir, task.configs);
11889
- const behaviors = [...await checkBehaviors(task, candidateDir), ...parity];
12688
+ const scorePins = composedPins(task.set, [opts.library !== void 0 ? path37.resolve(callerCwd, opts.library) : callerCwd]);
12689
+ const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
12690
+ const behaviors = [...await checkBehaviors(task, candidateDir), ...parity, ...composition];
11890
12691
  const parityCoverage = parity.length > 0 ? `${parity.filter((p) => p.pass).length}/${parity.length} hover-forced configs` : "not applicable (no hover-forced configs in this set)";
11891
12692
  const obj = objective(scores, behaviors);
11892
12693
  const total = scores.length + behaviors.length;
@@ -11911,12 +12712,16 @@ MISSING FEATURES (absent-ink clusters \u2014 recorded ink your render leaves now
11911
12712
  ${absentFindings.join("\n")}` : "";
11912
12713
  const certificationFeedback = `${absentBlock}
11913
12714
 
11914
- CERTIFICATION: ${certifiedReps.length}/${scores.length} configs at the certification bar (sim \u2265${certBar.sim} AND ink \u2265${certBar.ink}, exact values, after parity demotion \u2014 composition checks at verify can demote further).${certifiedReps.length < scores.length ? ` Below cert: ${scores.filter((sc) => !certifiedSet.has(sc.rep)).map((sc) => sc.rep).join(", ")}.` : ""}
12715
+ CERTIFICATION: ${certifiedReps.length}/${scores.length} configs at the certification bar (sim \u2265${certBar.sim} AND ink \u2265${certBar.ink}, exact values, after parity demotion \u2014 verify's A1.4 structural/crop checks can demote further).${certifiedReps.length < scores.length ? ` Below cert: ${scores.filter((sc) => !certifiedSet.has(sc.rep)).map((sc) => sc.rep).join(", ")}.` : ""}
11915
12716
  METRIC DEADBAND (read before iterating on near-misses): the scored similarity/ink deliberately tolerate \xB11px edge shift and antialiased-edge differences \u2014 cross-rasterizer noise absorption. A change entirely inside that band moves these numbers by EXACTLY ZERO (working as designed, not a stuck scorer). The per-config \`exact\` fields in the JSON are tolerance-free and move first: compare exact across rounds to confirm a small fix landed, and stop iterating when only exact moves \u2014 the bar reads the tolerant numbers.`;
11916
12717
  const stampNotice = `
11917
12718
 
11918
12719
  PROVENANCE STAMP: this scoring call itself (the Tendril CLI) just wrote/refreshed a comment on line 1 of styles.css carrying these scores, marked non-authoritative. If your editor or host reports styles.css was modified externally, that modification is this scorer \u2014 expected, not tampering. Keep the comment; it self-invalidates on any edit and \`tendril verify\` recomputes it.`;
11919
- const feedback = buildFeedback(scores, behaviors, bar, "files") + certificationFeedback + stampNotice + qualityFeedback;
12720
+ const compositionFeedback = scorePins.issues.length > 0 ? `
12721
+
12722
+ COMPOSITION PINS (ADR-013): ${scorePins.issues.length} confirmed pair(s) could not be pinned \u2014 verify will disclose each as confirmed-but-not-composed; not fixable by editing the candidate:
12723
+ ${scorePins.issues.map((i) => `- ${i}`).join("\n")}` : "";
12724
+ const feedback = buildFeedback(scores, behaviors, bar, "files") + certificationFeedback + compositionFeedback + stampNotice + qualityFeedback;
11920
12725
  const emitted = emitBundleV1({
11921
12726
  bundleDir: candidateDir,
11922
12727
  task,
@@ -11978,6 +12783,10 @@ PROVENANCE STAMP: this scoring call itself (the Tendril CLI) just wrote/refreshe
11978
12783
  // fixing verify's channel and not this one would have left the
11979
12784
  // reader who actually acts on it exactly where they were.
11980
12785
  coverage: { ...coverage, operabilityCheck: operability },
12786
+ // Same naming as verify's report block: the checks themselves ride
12787
+ // `behaviors` (composition: ids); this carries what CANNOT ride
12788
+ // there — which pairs pinned and which could not, with causes.
12789
+ ...scorePins.pins.length > 0 || scorePins.issues.length > 0 ? { crossComposition: { pinnedPairs: scorePins.pins.map((p) => p.pairKey), pinIssues: scorePins.issues } } : {},
11981
12790
  parityCoverage,
11982
12791
  evidenceDir,
11983
12792
  bundleManifest: emitted.written[0],
@@ -11989,12 +12798,14 @@ PROVENANCE STAMP: this scoring call itself (the Tendril CLI) just wrote/refreshe
11989
12798
  // bundles verify then FAILED on the interaction-evidence gate —
11990
12799
  // the oracle must say what verify will say, including this.
11991
12800
  ...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" } : {},
11992
- 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" }
12801
+ certification: { certified: certifiedReps.length, total: scores.length, bar: certBar, note: "tierOf on exact values + parity and absent-ink demotion; verify's A1.4 structural/crop checks can demote further" }
11993
12802
  },
11994
12803
  () => {
11995
12804
  for (const s of scores) process.stdout.write(`${s.pass ? certifiedSet.has(s.rep) ? "CERT" : "PASS" : "FAIL"} ${s.rep}: sim=${s.similarity} ink=${s.inkRecall}${s.error !== void 0 ? ` [${s.error}]` : ""}
11996
12805
  `);
11997
12806
  for (const b of behaviors) process.stdout.write(`${b.pass ? "PASS" : "FAIL"} ${b.id}${b.detail !== void 0 ? ` [${b.detail}]` : ""}
12807
+ `);
12808
+ for (const i of scorePins.issues) process.stdout.write(`PIN ISSUE ${i}
11998
12809
  `);
11999
12810
  process.stdout.write(`
12000
12811
  ${obj[0]}/${total} \xB7 certified ${certifiedReps.length}/${total} \xB7 floor=${obj[1].toFixed(3)} \xB7 mean=${obj[2].toFixed(3)} \xB7 evidence: ${evidenceDir}
@@ -12054,11 +12865,11 @@ var codeconnect_exports = {};
12054
12865
  __export(codeconnect_exports, {
12055
12866
  runCodeConnect: () => runCodeConnect
12056
12867
  });
12057
- import { existsSync as existsSync29, readFileSync as readFileSync25, writeFileSync as writeFileSync13 } from "node:fs";
12058
- import path36 from "node:path";
12868
+ import { existsSync as existsSync31, readFileSync as readFileSync27, writeFileSync as writeFileSync13 } from "node:fs";
12869
+ import path38 from "node:path";
12059
12870
  function runCodeConnect(opts) {
12060
12871
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
12061
- const bundleDir = path36.resolve(callerCwd, opts.bundleDir);
12872
+ const bundleDir = path38.resolve(callerCwd, opts.bundleDir);
12062
12873
  let url;
12063
12874
  try {
12064
12875
  url = new URL(opts.figmaUrl);
@@ -12074,7 +12885,7 @@ function runCodeConnect(opts) {
12074
12885
  }
12075
12886
  let manifest;
12076
12887
  try {
12077
- const read = readBundleManifest(readFileSync25(path36.join(bundleDir, "component.json"), "utf8"));
12888
+ const read = readBundleManifest(readFileSync27(path38.join(bundleDir, "component.json"), "utf8"));
12078
12889
  if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
12079
12890
  manifest = read.manifest;
12080
12891
  } catch (err) {
@@ -12084,8 +12895,8 @@ function runCodeConnect(opts) {
12084
12895
  remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
12085
12896
  });
12086
12897
  }
12087
- const setDir = path36.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
12088
- if (!existsSync29(path36.join(setDir, "recording-set.json"))) {
12898
+ const setDir = path38.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
12899
+ if (!existsSync31(path38.join(setDir, "recording-set.json"))) {
12089
12900
  fail(opts, ExitCode.InputValidation, {
12090
12901
  error: `recording set not found at ${setDir}`,
12091
12902
  code: "codeconnect-no-set",
@@ -12106,10 +12917,10 @@ function runCodeConnect(opts) {
12106
12917
  const component = api.component;
12107
12918
  const recManifest = loadManifest(setDir);
12108
12919
  const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
12109
- const meta = path36.join(setDir, r.slug, "get_metadata.json");
12110
- if (!existsSync29(meta)) return void 0;
12920
+ const meta = path38.join(setDir, r.slug, "get_metadata.json");
12921
+ if (!existsSync31(meta)) return void 0;
12111
12922
  try {
12112
- return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync25(meta, "utf8"))))?.[1];
12923
+ return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync27(meta, "utf8"))))?.[1];
12113
12924
  } catch {
12114
12925
  return void 0;
12115
12926
  }
@@ -12167,7 +12978,7 @@ function runCodeConnect(opts) {
12167
12978
  axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
12168
12979
  fragmentVars.push(varName);
12169
12980
  }
12170
- const entryRel = path36.relative(callerCwd, path36.join(bundleDir, manifest.entry));
12981
+ const entryRel = path38.relative(callerCwd, path38.join(bundleDir, manifest.entry));
12171
12982
  const trust = manifest.trustStatement.split("\n")[0] ?? "";
12172
12983
  const lines = [
12173
12984
  `// url=${opts.figmaUrl}`,
@@ -12188,7 +12999,7 @@ function runCodeConnect(opts) {
12188
12999
  `}`,
12189
13000
  ``
12190
13001
  ].join("\n");
12191
- const outFile = path36.resolve(callerCwd, opts.out ?? path36.join(bundleDir, `${component}.figma.ts`));
13002
+ const outFile = path38.resolve(callerCwd, opts.out ?? path38.join(bundleDir, `${component}.figma.ts`));
12192
13003
  writeFileSync13(outFile, lines);
12193
13004
  emitData(
12194
13005
  opts,
@@ -12227,18 +13038,18 @@ var init_codeconnect = __esm({
12227
13038
  });
12228
13039
 
12229
13040
  // packages/mcp/src/server.ts
12230
- import { createHash as createHash5 } from "node:crypto";
12231
- import { existsSync as existsSync30, mkdtempSync as mkdtempSync3, readFileSync as readFileSync26, readdirSync as readdirSync9, writeFileSync as writeFileSync14 } from "node:fs";
13041
+ import { createHash as createHash7 } from "node:crypto";
13042
+ import { existsSync as existsSync32, mkdtempSync as mkdtempSync3, readFileSync as readFileSync28, readdirSync as readdirSync12, writeFileSync as writeFileSync14 } from "node:fs";
12232
13043
  import os7 from "node:os";
12233
- import path37 from "node:path";
13044
+ import path39 from "node:path";
12234
13045
  import { fileURLToPath as fileURLToPath6 } from "node:url";
12235
- import { z as z12 } from "zod";
13046
+ import { z as z13 } from "zod";
12236
13047
  function sourceHash() {
12237
- const dir = path37.dirname(fileURLToPath6(import.meta.url));
12238
- const h = createHash5("sha256");
12239
- for (const f of readdirSync9(dir).filter((n) => n.endsWith(".ts")).sort()) {
13048
+ const dir = path39.dirname(fileURLToPath6(import.meta.url));
13049
+ const h = createHash7("sha256");
13050
+ for (const f of readdirSync12(dir).filter((n) => n.endsWith(".ts")).sort()) {
12240
13051
  h.update(f);
12241
- h.update(readFileSync26(path37.join(dir, f)));
13052
+ h.update(readFileSync28(path39.join(dir, f)));
12242
13053
  }
12243
13054
  return h.digest("hex").slice(0, 16);
12244
13055
  }
@@ -12246,27 +13057,27 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
12246
13057
  var init_server = __esm({
12247
13058
  "packages/mcp/src/server.ts"() {
12248
13059
  "use strict";
12249
- REPO_ROOT3 = path37.resolve(path37.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
12250
- CLI_BIN = path37.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
12251
- BUNDLED_CLI = path37.join(path37.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
12252
- CLI_SPAWN = existsSync30(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
12253
- str = (d) => z12.string().describe(d);
12254
- optStr = (d) => z12.string().optional().describe(d);
13060
+ REPO_ROOT3 = path39.resolve(path39.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
13061
+ CLI_BIN = path39.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
13062
+ BUNDLED_CLI = path39.join(path39.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
13063
+ CLI_SPAWN = existsSync32(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
13064
+ str = (d) => z13.string().describe(d);
13065
+ optStr = (d) => z13.string().optional().describe(d);
12255
13066
  TOOLS = [
12256
13067
  {
12257
13068
  name: "tendril_record_plan",
12258
13069
  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.",
12259
- schema: z12.object({
13070
+ schema: z13.object({
12260
13071
  setDir: str("recording set directory to create/resume"),
12261
13072
  component: str("component/system name"),
12262
13073
  // Parts array FIRST, same reason as ingest_rep: real responses
12263
13074
  // are usually multi-block, and the file param made plan the ONE
12264
13075
  // remaining hand-built-envelope entry point (run 10: the agent
12265
13076
  // wrote the file twice — once as text, once as JSON envelope).
12266
- metadataParts: z12.array(z12.string()).optional().describe("the frame-level get_metadata response blocks, every block in order, each verbatim \u2014 never hand-join or save them yourself; this is the NORMAL param"),
13077
+ metadataParts: z13.array(z13.string()).optional().describe("the frame-level get_metadata response blocks, every block in order, each verbatim \u2014 never hand-join or save them yourself; this is the NORMAL param"),
12267
13078
  metadata: optStr("ONLY when get_metadata genuinely returned one single block: its text verbatim (otherwise use metadataParts)"),
12268
- metadataFiles: z12.array(z12.string()).optional().describe('saved verbatim get_metadata envelope file paths \u2014 JSON shape {"content":[{"type":"text","text":"<frame \u2026>"}]}; optionally <file>@<frameId>. Prefer metadataParts: no file to write'),
12269
- defaults: z12.array(z12.string()).optional().describe(`axis defaults as "Axis=Value" (from the user's defaultsToConfirm answers; may re-plan a set with nothing recorded yet)`),
13079
+ metadataFiles: z13.array(z13.string()).optional().describe('saved verbatim get_metadata envelope file paths \u2014 JSON shape {"content":[{"type":"text","text":"<frame \u2026>"}]}; optionally <file>@<frameId>. Prefer metadataParts: no file to write'),
13080
+ defaults: z13.array(z13.string()).optional().describe(`axis defaults as "Axis=Value" (from the user's defaultsToConfirm answers; may re-plan a set with nothing recorded yet)`),
12270
13081
  componentSet: optStr("record only this component set (the user's pick from a multiple-component-sets error)"),
12271
13082
  figmaFile: optStr("the Figma file key from the design URL you were handed \u2014 figma.com/design/<KEY>/\u2026, pass exactly <KEY>. ALWAYS pass it on a fresh plan: it is the set's recorded file identity for cross-bundle composition (ADR-013), captured at plan time only and never backfillable later")
12272
13083
  }),
@@ -12280,7 +13091,7 @@ var init_server = __esm({
12280
13091
  const single = i["metadata"];
12281
13092
  const parts = i["metadataParts"];
12282
13093
  if (single !== void 0 || parts !== void 0) {
12283
- const tmp = path37.join(mkdtempSync3(path37.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
13094
+ const tmp = path39.join(mkdtempSync3(path39.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
12284
13095
  if (single !== void 0) {
12285
13096
  writeFileSync14(tmp, single);
12286
13097
  argvOut.push("--metadata-raw-file", tmp);
@@ -12302,10 +13113,10 @@ var init_server = __esm({
12302
13113
  {
12303
13114
  name: "tendril_permissions",
12304
13115
  description: "Install the Claude Code permission allowlist for the Tendril pipeline (write: true \u2014 merges the MCP per-tool entries, the run's shell surface, and project-scoped Write/Edit with deny guards for ./.claude and ./.git into the project's .claude/settings.local.json, idempotent, never touches other keys), or list the entries without writing. The shell/Write grants are CONVENIENCE, not a security boundary \u2014 the output's note names exactly what they trade; RELAY it with the offer. OFFER THIS AT PIPELINE START whenever NO merged Claude settings file (project .claude/settings.local.json or .claude/settings.json, or user ~/.claude/settings.json) contains tendril MCP entries \u2014 plugin installs use mcp__plugin_tendril_tendril__*, direct claude-mcp-add installs use mcp__<server>__* \u2014 when this session's tool names differ from the plugin defaults, pass figmaPrefix/tendrilPrefix with the prefixes you actually see, or the written entries never match. A FILE check, never prompt-watching \u2014 agents cannot observe permission prompts. ONE approval here replaces a prompt per pipeline call. Never run it unoffered; the user must reload the session for new settings to apply \u2014 say so.",
12305
- schema: z12.object({
12306
- write: z12.boolean().optional().describe("true = install into .claude/settings.local.json (the point of this tool); false/absent = just return the entries"),
12307
- figmaPrefix: z12.string().optional().describe("The Figma server's entry prefix AS THIS SESSION NAMES ITS TOOLS \u2014 the part before __get_metadata (e.g. mcp__figma-remote, mcp__plugin_figma_figma). Pass it whenever the session's Figma tools are not mcp__plugin_figma_figma__* \u2014 entries written under the wrong prefix never match anything (measured: six dead entries in a figma-remote session)."),
12308
- tendrilPrefix: z12.string().optional().describe("Same for the tendril server when this session's tendril tools are not mcp__plugin_tendril_tendril__* (e.g. mcp__tendril for claude mcp add installs).")
13116
+ schema: z13.object({
13117
+ write: z13.boolean().optional().describe("true = install into .claude/settings.local.json (the point of this tool); false/absent = just return the entries"),
13118
+ figmaPrefix: z13.string().optional().describe("The Figma server's entry prefix AS THIS SESSION NAMES ITS TOOLS \u2014 the part before __get_metadata (e.g. mcp__figma-remote, mcp__plugin_figma_figma). Pass it whenever the session's Figma tools are not mcp__plugin_figma_figma__* \u2014 entries written under the wrong prefix never match anything (measured: six dead entries in a figma-remote session)."),
13119
+ tendrilPrefix: z13.string().optional().describe("Same for the tendril server when this session's tendril tools are not mcp__plugin_tendril_tendril__* (e.g. mcp__tendril for claude mcp add installs).")
12309
13120
  }),
12310
13121
  argv: (i) => [
12311
13122
  "permissions",
@@ -12319,23 +13130,23 @@ var init_server = __esm({
12319
13130
  name: "tendril_doctor",
12320
13131
  annotations: { readOnlyHint: true },
12321
13132
  description: "Machine readiness + version status in one shot: installed vs latest version (with publish date and update remediation), browser identity, font-cache state, Figma desktop MCP reachability. Use to self-diagnose before recording/scoring, or whenever versions are in question. Exit 1 = something not ready; the report says exactly what and how to fix it.",
12322
- schema: z12.object({}),
13133
+ schema: z13.object({}),
12323
13134
  argv: () => ["doctor"]
12324
13135
  },
12325
13136
  {
12326
13137
  name: "tendril_record_next",
12327
13138
  annotations: { readOnlyHint: true },
12328
13139
  description: "Get the next pending recording instruction (which Figma MCP tool to call for which node, and how to save it). RARELY NEEDED: every ingest/fetch response already carries `next` \u2014 use this only to resume an interrupted session. The full queue is known from plan, so independent reps may be recorded in any order (and in parallel).",
12329
- schema: z12.object({ setDir: str("recording set directory") }),
13140
+ schema: z13.object({ setDir: str("recording set directory") }),
12330
13141
  argv: (i) => ["record", "next", "--set", i["setDir"]]
12331
13142
  },
12332
13143
  {
12333
13144
  name: "tendril_record_fetch",
12334
13145
  description: "Download a Figma asset URL (from get_screenshot's image_url) straight to disk and ingest it as the rep's envelope \u2014 the fallback when only the screenshot piece needs (re-)recording; for a rep's standard three recordings PREFER tendril_record_ingest_rep. Never download the image yourself: the bytes must not pass through your context.",
12335
- schema: z12.object({
13146
+ schema: z13.object({
12336
13147
  setDir: str("recording set directory"),
12337
13148
  rep: str("planned rep slug"),
12338
- tool: z12.enum(["get_screenshot"]).describe("get_screenshot"),
13149
+ tool: z13.enum(["get_screenshot"]).describe("get_screenshot"),
12339
13150
  url: str("image_url from the Figma response, verbatim")
12340
13151
  }),
12341
13152
  argv: (i) => ["record", "fetch", "--set", i["setDir"], "--rep", i["rep"], "--tool", i["tool"], "--url", i["url"]]
@@ -12343,15 +13154,15 @@ var init_server = __esm({
12343
13154
  {
12344
13155
  name: "tendril_record_ingest_rep",
12345
13156
  description: "Ingest a rep's ENTIRE recording in ONE call \u2014 the get_metadata response, the get_design_context response, and the get_screenshot image_url together. Make the three Figma calls first, in protocol order (get_metadata, then get_design_context with excludeScreenshot=true, then get_screenshot), then pass all three here VERBATIM. PREFER THIS over three separate ingest/fetch calls: one approvable operation per rep instead of three. Pieces land independently: on a partial failure the error names exactly which piece(s) to re-record \u2014 the rest are already on disk. The response carries `next` and, for design context, `assets` (auto-fetched server-side; only listed failures need record_asset).",
12346
- schema: z12.object({
13157
+ schema: z13.object({
12347
13158
  setDir: str("recording set directory"),
12348
13159
  rep: str("planned rep slug"),
12349
13160
  // Parts arrays FIRST: in the field, EVERY real Figma response is
12350
13161
  // multi-block (run 6: 49/49 reps — metadata 2 blocks, design
12351
13162
  // context 5-6), so the arrays are the norm and the single-string
12352
13163
  // params the rare case, not the reverse.
12353
- metadataParts: z12.array(z12.string()).optional().describe("get_metadata response blocks, every block in order, each verbatim \u2014 never hand-join blocks yourself. Multi-block responses are common (run 6: 49/49 reps); a single block wrapped in a one-element array is equally fine."),
12354
- contextParts: z12.array(z12.string()).optional().describe("get_design_context response blocks, every block in order, each verbatim \u2014 the NORMAL param (real responses arrive as 5-6 blocks)"),
13164
+ metadataParts: z13.array(z13.string()).optional().describe("get_metadata response blocks, every block in order, each verbatim \u2014 never hand-join blocks yourself. Multi-block responses are common (run 6: 49/49 reps); a single block wrapped in a one-element array is equally fine."),
13165
+ contextParts: z13.array(z13.string()).optional().describe("get_design_context response blocks, every block in order, each verbatim \u2014 the NORMAL param (real responses arrive as 5-6 blocks)"),
12355
13166
  screenshotUrl: optStr("image_url from the get_screenshot response, verbatim \u2014 the CLI downloads it; the bytes never pass through your context"),
12356
13167
  metadata: optStr("ONLY when get_metadata genuinely returned one single block: its text verbatim (otherwise use metadataParts)"),
12357
13168
  context: optStr("ONLY when get_design_context genuinely returned one single block: its text verbatim (otherwise use contextParts)")
@@ -12363,7 +13174,7 @@ var init_server = __esm({
12363
13174
  const bridge = (label, single, parts) => {
12364
13175
  if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
12365
13176
  if (single === void 0 && parts === void 0) return;
12366
- const tmp = path37.join(mkdtempSync3(path37.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
13177
+ const tmp = path39.join(mkdtempSync3(path39.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
12367
13178
  if (single !== void 0) {
12368
13179
  writeFileSync14(tmp, single);
12369
13180
  argvOut.push(`--${label}-file`, tmp);
@@ -12384,7 +13195,7 @@ var init_server = __esm({
12384
13195
  {
12385
13196
  name: "tendril_record_ingest",
12386
13197
  description: "Single-piece ingest of a VERBATIM Figma tool-response \u2014 the fallback path (re-recording one failed piece, set-level get_variable_defs, get_metadata_interior for mains); for a rep's standard three recordings PREFER tendril_record_ingest_rep, which takes them all in one call. Pass `text` (single block) or `texts` (response split into multiple output blocks \u2014 each block verbatim, in order; NEVER hand-join them): the CLI constructs the envelope from the same bytes. The response includes `next` and, for get_design_context, `assets` (auto-fetched; only listed failures need manual handling).",
12387
- schema: z12.object({
13198
+ schema: z13.object({
12388
13199
  setDir: str("recording set directory"),
12389
13200
  rep: str("planned rep slug, or __set__ for the set-level get_variable_defs"),
12390
13201
  // Enumerated, not a free string: this value reaches a file path.
@@ -12392,9 +13203,9 @@ var init_server = __esm({
12392
13203
  // (--tool "../../outside/victim" replaced a file outside the set,
12393
13204
  // exit 0). The sink in session.ts now contains the path too — this
12394
13205
  // is the second layer, and it makes the tool self-documenting.
12395
- tool: z12.enum(["get_design_context", "get_metadata", "get_screenshot", "get_variable_defs", "get_metadata_interior"]),
13206
+ tool: z13.enum(["get_design_context", "get_metadata", "get_screenshot", "get_variable_defs", "get_metadata_interior"]),
12396
13207
  text: optStr("the tool response text VERBATIM \u2014 exactly as returned, unmodified (single-block responses; text tools only \u2014 screenshots go through record_fetch)"),
12397
- texts: z12.array(z12.string()).optional().describe("when the response arrived as MULTIPLE output blocks: every block, in order, each verbatim \u2014 never hand-join blocks yourself"),
13208
+ texts: z13.array(z13.string()).optional().describe("when the response arrived as MULTIPLE output blocks: every block, in order, each verbatim \u2014 never hand-join blocks yourself"),
12398
13209
  file: optStr("path to a saved envelope JSON (alternative to text/texts)")
12399
13210
  }),
12400
13211
  // The text rides a temp file, never argv: Windows caps a command
@@ -12406,7 +13217,7 @@ var init_server = __esm({
12406
13217
  const file = i["file"];
12407
13218
  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)");
12408
13219
  if (file !== void 0) return [...base, "--file", file];
12409
- const tmp = path37.join(mkdtempSync3(path37.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
13220
+ const tmp = path39.join(mkdtempSync3(path39.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
12410
13221
  if (text !== void 0) {
12411
13222
  writeFileSync14(tmp, text);
12412
13223
  return [...base, "--file", tmp, "--raw"];
@@ -12418,7 +13229,7 @@ var init_server = __esm({
12418
13229
  {
12419
13230
  name: "tendril_record_asset",
12420
13231
  description: "FALLBACK ONLY \u2014 ingest auto-fetches design-context assets; use this just for assets listed in an ingest response's `assets.failed`. Batch mode: pass `dir` to ingest every asset-*.<ext> in a directory in ONE call. SVGs with active content are rejected; sizes are capped.",
12421
- schema: z12.object({
13232
+ schema: z13.object({
12422
13233
  setDir: str("recording set directory"),
12423
13234
  rep: str("planned rep slug"),
12424
13235
  name: optStr("asset-<id>.<ext> (single-asset mode)"),
@@ -12439,13 +13250,13 @@ var init_server = __esm({
12439
13250
  name: "tendril_record_status",
12440
13251
  annotations: { readOnlyHint: true },
12441
13252
  description: "Recording-set completeness: per-rep recorded/missing tools, files that exist but are unusable (invalid \u2014 re-record those), and whether the set-level token map is recorded. `complete` means USABLE by the next pipeline step, including the set-level get_variable_defs.",
12442
- schema: z12.object({ setDir: str("recording set directory") }),
13253
+ schema: z13.object({ setDir: str("recording set directory") }),
12443
13254
  argv: (i) => ["record", "status", "--set", i["setDir"]]
12444
13255
  },
12445
13256
  {
12446
13257
  name: "tendril_engine_brief",
12447
13258
  description: "AGENT-HARNESS engine, step 1: emits the task payload file (system brief + every recorded config's emission, box, assets, tokens) and the protocol. YOU (the calling agent) implement the bundle; the CLI is the only judge. Read the payload file completely before proposing.",
12448
- schema: z12.object({
13259
+ schema: z13.object({
12449
13260
  taskOrSet: str("a recording-set directory (from tendril_record), or a reference task name \u2014 an unknown name returns the valid list in the error"),
12450
13261
  // Required by design, not convenience: the model choice must be
12451
13262
  // settled BEFORE generation starts. A smoke run picked its own
@@ -12455,7 +13266,8 @@ var init_server = __esm({
12455
13266
  // choose, declare, and state the reason in your report.
12456
13267
  model: str("the model that will WRITE the implementation \u2014 ask the user when interactive; declare your reasoned choice when not"),
12457
13268
  bar: optStr("pass (default) or cert"),
12458
- out: optStr("payload file path override")
13269
+ out: optStr("payload file path override"),
13270
+ library: optStr("workspace root holding confirmed partners generated bundles (ADR-013 composed pins; default: the server's working directory)")
12459
13271
  }),
12460
13272
  argv: (i) => [
12461
13273
  "engine",
@@ -12464,19 +13276,21 @@ var init_server = __esm({
12464
13276
  "--model",
12465
13277
  i["model"],
12466
13278
  ...i["bar"] !== void 0 ? ["--bar", i["bar"]] : [],
12467
- ...i["out"] !== void 0 ? ["--out", i["out"]] : []
13279
+ ...i["out"] !== void 0 ? ["--out", i["out"]] : [],
13280
+ ...i["library"] !== void 0 ? ["--library", i["library"]] : []
12468
13281
  ]
12469
13282
  },
12470
13283
  {
12471
13284
  name: "tendril_engine_score",
12472
13285
  description: "AGENT-HARNESS engine, step 2 (the oracle): scores a candidate bundle directory against recorded truth \u2014 per-config pixels, behaviors, state parity (recording-selected \u2014 a bundle cannot unschedule it) \u2014 and returns feedback plus evidence artifacts. Iterate until allPass or two non-improving rounds. Only THIS tool's output counts as a score; never claim numbers yourself.",
12473
- schema: z12.object({
13286
+ schema: z13.object({
12474
13287
  taskOrSet: str("reference task name or recording-set directory"),
12475
13288
  candidateDir: str("directory containing the proposed bundle files"),
12476
13289
  bar: optStr("pass (default) or cert"),
12477
13290
  host: optStr("your host identity (e.g. claude-code, cursor, codex) \u2014 recorded as self-reported provenance"),
12478
13291
  model: str("the model that proposed the candidate \u2014 REQUIRED; recorded as self-reported provenance, and the CLI refuses to score without it"),
12479
- rebind: z12.boolean().optional().describe("explicitly re-bind an already-bound bundle to a DIFFERENT recording set \u2014 scoring refuses this otherwise, because rebinding silently rewrites the bundle's verification identity; only pass after telling the user")
13292
+ rebind: z13.boolean().optional().describe("explicitly re-bind an already-bound bundle to a DIFFERENT recording set \u2014 scoring refuses this otherwise, because rebinding silently rewrites the bundle's verification identity; only pass after telling the user"),
13293
+ library: optStr("workspace root holding confirmed partners generated bundles (ADR-013 composed pins; default: the server's working directory) \u2014 pass the same root the brief used")
12480
13294
  }),
12481
13295
  argv: (i) => [
12482
13296
  "engine",
@@ -12487,13 +13301,14 @@ var init_server = __esm({
12487
13301
  ...i["host"] !== void 0 ? ["--host", i["host"]] : [],
12488
13302
  "--model",
12489
13303
  i["model"],
12490
- ...i["rebind"] === true ? ["--rebind"] : []
13304
+ ...i["rebind"] === true ? ["--rebind"] : [],
13305
+ ...i["library"] !== void 0 ? ["--library", i["library"]] : []
12491
13306
  ]
12492
13307
  },
12493
13308
  {
12494
13309
  name: "tendril_codeconnect",
12495
13310
  description: "Emit a Figma Code Connect template (.figma.ts) for a certified bundle \u2014 every Figma variant value mapped to its verified prop fragment from recorded truth, stamped with the bundle's trust statement. EXTRA VALUE step after verify passes: offer it to the user. Publishing is the USER'S action (their Figma token, Organization/Enterprise plan) \u2014 via npx @figma/code-connect connect publish, or the Figma MCP's own add_code_connect_map/send_code_connect_mappings tools if available in this session.",
12496
- schema: z12.object({
13311
+ schema: z13.object({
12497
13312
  bundleDir: str("bundle directory (carries component.json)"),
12498
13313
  figmaUrl: str("figma.com /design/ URL of the COMPONENT SET, with node-id (ask the user to Copy link to selection if you don't have it)"),
12499
13314
  set: optStr("recording set override (default: the bundle's provenance path)"),
@@ -12511,27 +13326,29 @@ var init_server = __esm({
12511
13326
  {
12512
13327
  name: "tendril_verify",
12513
13328
  description: "Verify/check that a component matches its Figma design \u2014 use when the user asks whether an implementation is faithful to the design, or to re-certify an existing Tendril bundle. Recomputes full verification (per-config status, behaviors, composition, evidence artifacts). Free, account-less, network-less \u2014 the trust anchor. Exit 5 means below the target bar with an honest report \u2014 sub-bar scores, or at bar cert a config demoted by absent-ink clusters.",
12514
- schema: z12.object({
13329
+ schema: z13.object({
12515
13330
  bundleDir: str("bundle directory to verify"),
12516
13331
  bar: optStr("pass (default) or cert"),
12517
- set: optStr("recording-set directory override")
13332
+ set: optStr("recording-set directory override"),
13333
+ library: optStr("workspace root holding confirmed partners generated bundles (ADR-013 composed pins; default: the server's working directory)")
12518
13334
  }),
12519
13335
  argv: (i) => [
12520
13336
  "verify",
12521
13337
  i["bundleDir"],
12522
13338
  ...i["bar"] !== void 0 ? ["--bar", i["bar"]] : [],
12523
- ...i["set"] !== void 0 ? ["--set", i["set"]] : []
13339
+ ...i["set"] !== void 0 ? ["--set", i["set"]] : [],
13340
+ ...i["library"] !== void 0 ? ["--library", i["library"]] : []
12524
13341
  ]
12525
13342
  },
12526
13343
  {
12527
13344
  name: "tendril_generate_curated",
12528
13345
  description: "CURATED engine (explicit alternative path): generation by an allowlisted API model over the user's OpenRouter-compatible key, with cost consent, hard spend caps, and resume. Use ONLY when the user asks for API-model generation instead of implementing it yourself.",
12529
- schema: z12.object({
13346
+ schema: z13.object({
12530
13347
  input: str("reference task name or recording-set directory"),
12531
13348
  model: optStr("OpenRouter model id (default: allowlist pointer)"),
12532
13349
  bar: optStr("pass (default) or cert"),
12533
13350
  cap: optStr("spend cap in USD (default 1.50)"),
12534
- yes: z12.boolean().optional().describe("accept the cost consent (the user must have approved the spend)")
13351
+ yes: z13.boolean().optional().describe("accept the cost consent (the user must have approved the spend)")
12535
13352
  }),
12536
13353
  argv: (i) => [
12537
13354
  "generate",
@@ -12562,13 +13379,13 @@ __export(permissions_exports, {
12562
13379
  runPermissions: () => runPermissions,
12563
13380
  writeSelection: () => writeSelection
12564
13381
  });
12565
- import { existsSync as existsSync31, mkdirSync as mkdirSync9, readFileSync as readFileSync27, writeFileSync as writeFileSync15 } from "node:fs";
13382
+ import { existsSync as existsSync33, mkdirSync as mkdirSync9, readFileSync as readFileSync29, writeFileSync as writeFileSync15 } from "node:fs";
12566
13383
  import os8 from "node:os";
12567
- import path38 from "node:path";
13384
+ import path40 from "node:path";
12568
13385
  function mergeAllowlist(file, entries, denyEntries = []) {
12569
13386
  let settings = {};
12570
- if (existsSync31(file) && readFileSync27(file, "utf8").trim() !== "") {
12571
- settings = JSON.parse(readFileSync27(file, "utf8"));
13387
+ if (existsSync33(file) && readFileSync29(file, "utf8").trim() !== "") {
13388
+ settings = JSON.parse(readFileSync29(file, "utf8"));
12572
13389
  if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
12573
13390
  }
12574
13391
  const permissions = settings["permissions"] ??= {};
@@ -12588,7 +13405,7 @@ function mergeAllowlist(file, entries, denyEntries = []) {
12588
13405
  }
12589
13406
  if (added.length > 0 || denyAdded.length > 0) {
12590
13407
  allow.push(...added);
12591
- mkdirSync9(path38.dirname(file), { recursive: true });
13408
+ mkdirSync9(path40.dirname(file), { recursive: true });
12592
13409
  writeFileSync15(file, `${JSON.stringify(settings, null, 2)}
12593
13410
  `);
12594
13411
  }
@@ -12653,7 +13470,7 @@ async function runPermissions(flags) {
12653
13470
  }
12654
13471
  if (flags.write) {
12655
13472
  const base = process.env["INIT_CWD"] ?? process.cwd();
12656
- const file = flags.user ? path38.join(os8.homedir(), ".claude", "settings.json") : path38.join(base, ".claude", "settings.local.json");
13473
+ const file = flags.user ? path40.join(os8.homedir(), ".claude", "settings.json") : path40.join(base, ".claude", "settings.local.json");
12657
13474
  const { entries, denyEntries } = writeSelection(result, flags.user === true);
12658
13475
  if (flags.dryRun) {
12659
13476
  emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
@@ -12796,30 +13613,255 @@ var init_permissions = __esm({
12796
13613
  }
12797
13614
  });
12798
13615
 
13616
+ // packages/cli/src/commands/compose.ts
13617
+ var compose_exports = {};
13618
+ __export(compose_exports, {
13619
+ COMPOSE_DESCRIPTION: () => COMPOSE_DESCRIPTION,
13620
+ runCompose: () => runCompose
13621
+ });
13622
+ import { createHash as createHash8 } from "node:crypto";
13623
+ import { existsSync as existsSync34, readFileSync as readFileSync30 } from "node:fs";
13624
+ import path41 from "node:path";
13625
+ function substitutionPairs(edges, hostSet) {
13626
+ const pairs = /* @__PURE__ */ new Map();
13627
+ for (const e of edges) {
13628
+ if (e.hostSet !== hostSet || e.kind !== "substitution" || e.pose === void 0) continue;
13629
+ const dirs = e.partners.map((p) => p.dir);
13630
+ const key = pairKeyFor(hostSet, dirs);
13631
+ if (!pairs.has(key)) {
13632
+ pairs.set(key, {
13633
+ key,
13634
+ partnerDirs: dirs,
13635
+ displayName: e.partners[0].displayName,
13636
+ ...e.partners[0].figmaFile !== void 0 ? { figmaFile: e.partners[0].figmaFile } : {},
13637
+ instances: [],
13638
+ disclosures: []
13639
+ });
13640
+ }
13641
+ const pair = pairs.get(key);
13642
+ pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId });
13643
+ for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
13644
+ }
13645
+ return [...pairs.values()];
13646
+ }
13647
+ function runCompose(flags) {
13648
+ if (flags.describe) {
13649
+ printDescription(COMPOSE_DESCRIPTION);
13650
+ return;
13651
+ }
13652
+ const base = process.env["INIT_CWD"] ?? process.cwd();
13653
+ const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path41.resolve(base, d)) : [base];
13654
+ if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
13655
+ fail(flags, ExitCode.InputValidation, {
13656
+ error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
13657
+ code: "compose-decision-without-set",
13658
+ remediation: "Add --set <host recording-set dir>, or drop the decision flags to list opportunities."
13659
+ });
13660
+ }
13661
+ if (flags.set !== void 0) {
13662
+ runComposeConfirm(flags, path41.resolve(base, flags.set), roots);
13663
+ return;
13664
+ }
13665
+ const index = buildComposeIndex(roots);
13666
+ const edges = composeReport(index);
13667
+ emitData(flags, { sets: index.map((s) => s.dir), edges, note: NOTE }, () => {
13668
+ process.stdout.write(`indexed ${index.length} recording set(s) under ${roots.join(", ")}
13669
+ `);
13670
+ if (index.length === 0) {
13671
+ process.stdout.write("no recording sets found \u2014 point --library at a directory that holds recording-set.json sets\n");
13672
+ return;
13673
+ }
13674
+ if (edges.length === 0) {
13675
+ process.stdout.write("no composition edges: no recorded instance references another recorded component (externals stay disclosed in each set's own reports)\n");
13676
+ return;
13677
+ }
13678
+ let lastHost = "";
13679
+ for (const e of edges) {
13680
+ const host = `${path41.basename(e.hostSet)}`;
13681
+ if (host !== lastHost) {
13682
+ process.stdout.write(`
13683
+ ${host}
13684
+ `);
13685
+ lastHost = host;
13686
+ }
13687
+ const partners = e.partners.map((p) => p.displayName).join(" / ") || "\u2014";
13688
+ const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path41.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
13689
+ process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
13690
+ `);
13691
+ for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
13692
+ `);
13693
+ }
13694
+ process.stdout.write(`
13695
+ ${NOTE}
13696
+ `);
13697
+ });
13698
+ }
13699
+ function runComposeConfirm(flags, hostSet, roots) {
13700
+ if (!existsSync34(path41.join(hostSet, "recording-set.json"))) {
13701
+ fail(flags, ExitCode.InputValidation, {
13702
+ error: `no recording-set.json in ${hostSet}`,
13703
+ code: "no-recording-set",
13704
+ remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
13705
+ });
13706
+ }
13707
+ const scanRoots = [.../* @__PURE__ */ new Set([...roots, path41.dirname(hostSet)])];
13708
+ const index = buildComposeIndex(scanRoots);
13709
+ const edges = composeReport(index);
13710
+ const pairs = substitutionPairs(edges, hostSet);
13711
+ const { raw } = readManifestFile(hostSet);
13712
+ const rawStanding = Array.isArray(raw["compositions"]) ? raw["compositions"] : [];
13713
+ const standing = [];
13714
+ for (let i = 0; i < rawStanding.length; i++) {
13715
+ const parsed = CompositionEntrySchema.safeParse(rawStanding[i]);
13716
+ if (!parsed.success) {
13717
+ fail(flags, ExitCode.InputValidation, {
13718
+ error: `compositions[${i}] in ${hostSet}/recording-set.json is not a valid v1 composition entry (${parsed.error.issues[0]?.message ?? "invalid"})`,
13719
+ code: "compositions-entry-invalid",
13720
+ remediation: "The manifest's compositions extension holds human decisions and is provenance-hashed \u2014 repair or remove the malformed entry by hand, then re-run. Nothing was changed."
13721
+ });
13722
+ }
13723
+ standing.push(parsed.data);
13724
+ }
13725
+ const decidedKeys = new Set(standing.map((c) => fromStoredRel(c.partner.key)));
13726
+ const open = pairs.filter((p) => !decidedKeys.has(p.key));
13727
+ const printProposal = () => {
13728
+ for (const s of standing) process.stdout.write(`standing ${s.status.toUpperCase()}: ${s.partner.displayName} [${s.partner.key}] (${s.instances.length} instance(s)) \u2014 asked once, not re-asked
13729
+ `);
13730
+ if (open.length === 0) {
13731
+ process.stdout.write(standing.length > 0 ? "no undecided composition pairs for this host\n" : "no id-backed composition pairs found for this host (name-only proposals, if any, are not confirmable \u2014 see compose --list)\n");
13732
+ return;
13733
+ }
13734
+ for (const p of open) {
13735
+ process.stdout.write(`
13736
+ PAIR ${p.displayName} [${p.key}]${p.figmaFile !== void 0 ? ` file ${p.figmaFile}` : " \u2014 file identity NOT captured"}
13737
+ `);
13738
+ for (const i of p.instances) process.stdout.write(` ${i.hostRep}/${i.instanceId} \u2192 pose ${i.poseVariantNodeId}
13739
+ `);
13740
+ for (const d of p.disclosures) process.stdout.write(` \xB7 ${d}
13741
+ `);
13742
+ }
13743
+ };
13744
+ const deciding = flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0;
13745
+ if (open.length === 0 || !deciding) {
13746
+ emitData(flags, { hostSet, openPairs: open, standing, note: "confirmations are human-only; declines persist; name-only proposals are never confirmable" }, printProposal);
13747
+ } else if (!flags.json) {
13748
+ printProposal();
13749
+ }
13750
+ if (open.length === 0) return;
13751
+ if (flags.confirmCompositions !== true && (flags.decline === void 0 || flags.decline.length === 0)) {
13752
+ fail(flags, ExitCode.ConfirmationRequired, {
13753
+ error: "composition pairs require a human decision",
13754
+ code: "compositions-unconfirmed",
13755
+ remediation: `Review the pairs above, then a HUMAN re-runs \`${tendrilCommand(`compose --set ${hostSet} --confirm-compositions`)}\` (confirm all listed) and/or \`--decline <pair-key>\` in their own terminal. Agents: surface this to your operator \u2014 the flag is refused without an interactive terminal, and composing without confirmation never happens.`
13756
+ });
13757
+ }
13758
+ if (process.stdin.isTTY !== true) {
13759
+ fail(flags, ExitCode.ConfirmationRequired, {
13760
+ error: "--confirm-compositions and --decline require an interactive terminal \u2014 this decision is human-only",
13761
+ code: "compositions-confirmation-not-interactive",
13762
+ remediation: `A human runs \`${tendrilCommand(`compose --set ${hostSet} --confirm-compositions`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
13763
+ });
13764
+ }
13765
+ const declineKeys = new Set(flags.decline ?? []);
13766
+ for (const k of declineKeys) {
13767
+ if (!open.some((p) => p.key === k)) {
13768
+ fail(flags, ExitCode.InputValidation, {
13769
+ error: `--decline "${k}" names no open pair (open: ${open.map((p) => p.key).join(", ")})`,
13770
+ code: "unknown-composition-pair",
13771
+ remediation: "Spell the pair key exactly as printed in brackets."
13772
+ });
13773
+ }
13774
+ }
13775
+ const entries = open.map((p) => ({
13776
+ v: 1,
13777
+ partner: {
13778
+ key: p.key,
13779
+ displayName: p.displayName,
13780
+ ...p.figmaFile !== void 0 ? { figmaFile: p.figmaFile } : {},
13781
+ // The partner's manifest BYTES, pinned at decision time and
13782
+ // keyed by the same host-relative identity as the pair key —
13783
+ // any partner re-plan/roles/composition write flips it, which
13784
+ // is the staleness signal later slices compare against. (The
13785
+ // full recording-set hash join lands with pin authoring, where
13786
+ // task configs exist.)
13787
+ manifestSha256: Object.fromEntries(
13788
+ p.partnerDirs.map((d) => [path41.relative(hostSet, d), createHash8("sha256").update(readFileSync30(path41.join(d, "recording-set.json"))).digest("hex")])
13789
+ )
13790
+ },
13791
+ instances: p.instances,
13792
+ status: declineKeys.has(p.key) ? "declined" : "confirmed"
13793
+ }));
13794
+ const decided = flags.confirmCompositions === true ? entries : entries.filter((e) => declineKeys.has(e.partner.key));
13795
+ const merged = { ...raw, compositions: [...standing, ...decided] };
13796
+ writeManifest(hostSet, merged);
13797
+ warn(
13798
+ flags,
13799
+ "the host manifest changed \u2014 its bytes are hashed into bundle provenance, so bundles generated from this set before this decision now carry a stale set identity; regenerate to compose (`tendril engine brief` carries the pins)"
13800
+ );
13801
+ emitData(flags, { hostSet, openPairs: open, standing, written: decided, note: "confirmations are human-only; declines persist; name-only proposals are never confirmable" }, () => {
13802
+ for (const e of decided) process.stdout.write(`${e.status.toUpperCase()}: ${e.partner.displayName} [${e.partner.key}] (${e.instances.length} instance(s))
13803
+ `);
13804
+ });
13805
+ }
13806
+ var COMPOSE_DESCRIPTION, NOTE;
13807
+ var init_compose2 = __esm({
13808
+ "packages/cli/src/commands/compose.ts"() {
13809
+ "use strict";
13810
+ init_src3();
13811
+ init_src();
13812
+ init_describe();
13813
+ init_invocation();
13814
+ init_output();
13815
+ COMPOSE_DESCRIPTION = {
13816
+ name: "compose",
13817
+ summary: "Composition opportunities across a workspace's recording sets (ADR-013): --list is the read-only discovery surface; --set persists a HUMAN's confirm/decline decisions into the host manifest. Nothing composes yet \u2014 compose-on-generate is a later slice.",
13818
+ args: [],
13819
+ flags: [
13820
+ { flag: "--list", description: "Derive the index and report edges (default)" },
13821
+ { flag: "--library <dir...>", description: "Workspace root(s) to scan for recording sets", default: "current directory" },
13822
+ { flag: "--set <dir>", description: "Confirmation mode: propose this HOST set's id-backed pairs for a human decision (confirmations and declines persist in the host manifest)" },
13823
+ { flag: "--confirm-compositions", description: "HUMAN-ONLY: confirm every open pair printed for --set \u2014 refused without an interactive terminal; agents surface the proposal to their operator" },
13824
+ { flag: "--decline <pair-key...>", description: "HUMAN-ONLY: persistently decline the named pair(s) \u2014 asked once, never re-asked" },
13825
+ { flag: "--json", description: "Machine-readable output" }
13826
+ ],
13827
+ output: {
13828
+ sets: "string[] \u2014 recording sets indexed (--list)",
13829
+ edges: "per-instance edges: kind (substitution | nested | ask | proposal | external), partners, pose (variant node id + per-set rep slugs), disclosures (--list)",
13830
+ openPairs: "with --set: undecided id-backed pairs awaiting a human decision",
13831
+ standing: "with --set: persisted confirmations/declines (asked once)",
13832
+ note: "string \u2014 what this command does NOT do"
13833
+ },
13834
+ exitCodes: { 0: "report printed (an empty one is a report, not an error)", 4: "with --set: open pairs need a human decision, or the flag arrived without an interactive terminal", 3: "with --set: bad host dir or unknown --decline key" },
13835
+ examples: ["tendril compose --list", "tendril compose --list --library ./recordings --json", "tendril compose --set ./recordings/dialog --confirm-compositions"]
13836
+ };
13837
+ NOTE = "Discovery only: these are PROPOSALS under the audited join rule (id evidence decides; name evidence only proposes). Nothing composes yet \u2014 the human confirmation channel and compose-on-generate land in later slices. Substitution-grade edges do NOT assert pixel-neutrality: instance overrides are measured-real and the score-time check is the judge.";
13838
+ }
13839
+ });
13840
+
12799
13841
  // packages/cli/src/commands/inspect.ts
12800
13842
  var inspect_exports = {};
12801
13843
  __export(inspect_exports, {
12802
13844
  INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
12803
13845
  runInspect: () => runInspect
12804
13846
  });
12805
- import { existsSync as existsSync32, readFileSync as readFileSync28, writeFileSync as writeFileSync16 } from "node:fs";
12806
- import path39 from "node:path";
13847
+ import { existsSync as existsSync35, readFileSync as readFileSync31, writeFileSync as writeFileSync16 } from "node:fs";
13848
+ import path42 from "node:path";
12807
13849
  async function runInspect(opts) {
12808
13850
  if (opts.describe) {
12809
13851
  printDescription(INSPECT_DESCRIPTION);
12810
13852
  return;
12811
13853
  }
12812
- const bundleDir = path39.resolve(opts.bundleDir);
12813
- const evidenceDir = path39.join(bundleDir, "verify-evidence");
12814
- const manifestPath2 = path39.join(bundleDir, "component.json");
12815
- if (!existsSync32(evidenceDir) || !existsSync32(manifestPath2)) {
13854
+ const bundleDir = path42.resolve(opts.bundleDir);
13855
+ const evidenceDir = path42.join(bundleDir, "verify-evidence");
13856
+ const manifestPath2 = path42.join(bundleDir, "component.json");
13857
+ if (!existsSync35(evidenceDir) || !existsSync35(manifestPath2)) {
12816
13858
  fail(opts, ExitCode.InputValidation, {
12817
- error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync32(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
13859
+ error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync35(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
12818
13860
  code: "no-evidence",
12819
13861
  remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
12820
13862
  });
12821
13863
  }
12822
- const { manifest } = readBundleManifest(readFileSync28(manifestPath2, "utf8"));
13864
+ const { manifest } = readBundleManifest(readFileSync31(manifestPath2, "utf8"));
12823
13865
  if (manifest === void 0) {
12824
13866
  fail(opts, ExitCode.InputValidation, {
12825
13867
  error: "component.json did not parse as a bundle manifest",
@@ -12827,8 +13869,8 @@ async function runInspect(opts) {
12827
13869
  remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
12828
13870
  });
12829
13871
  }
12830
- const setDir = path39.resolve(opts.set ?? manifest.provenance.recordingSet.path);
12831
- const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync32(path39.join(evidenceDir, `${rep}-ref.png`)) && existsSync32(path39.join(evidenceDir, `${rep}-render.png`)));
13872
+ const setDir = path42.resolve(opts.set ?? manifest.provenance.recordingSet.path);
13873
+ const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync35(path42.join(evidenceDir, `${rep}-ref.png`)) && existsSync35(path42.join(evidenceDir, `${rep}-render.png`)));
12832
13874
  if (reps.length === 0) {
12833
13875
  fail(opts, ExitCode.InputValidation, {
12834
13876
  error: "verify-evidence holds no ref/render pairs for this bundle's configs",
@@ -12839,15 +13881,15 @@ async function runInspect(opts) {
12839
13881
  let crops = 0;
12840
13882
  const sections = [];
12841
13883
  for (const rep of reps) {
12842
- const ref = new Uint8Array(readFileSync28(path39.join(evidenceDir, `${rep}-ref.png`)));
12843
- const render = new Uint8Array(readFileSync28(path39.join(evidenceDir, `${rep}-render.png`)));
13884
+ const ref = new Uint8Array(readFileSync31(path42.join(evidenceDir, `${rep}-ref.png`)));
13885
+ const render = new Uint8Array(readFileSync31(path42.join(evidenceDir, `${rep}-render.png`)));
12844
13886
  const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
12845
13887
  const cells = [];
12846
13888
  for (const [i, n] of nodes.entries()) {
12847
13889
  const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
12848
13890
  try {
12849
- writeFileSync16(path39.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
12850
- writeFileSync16(path39.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
13891
+ writeFileSync16(path42.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
13892
+ writeFileSync16(path42.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
12851
13893
  } catch {
12852
13894
  continue;
12853
13895
  }
@@ -12860,7 +13902,7 @@ async function runInspect(opts) {
12860
13902
  `<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>`
12861
13903
  );
12862
13904
  }
12863
- const sheet = path39.join(evidenceDir, "inspect.html");
13905
+ const sheet = path42.join(evidenceDir, "inspect.html");
12864
13906
  writeFileSync16(
12865
13907
  sheet,
12866
13908
  `<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
@@ -12938,17 +13980,17 @@ __export(generate_recorded_exports, {
12938
13980
  runGenerateRecorded: () => runGenerateRecorded
12939
13981
  });
12940
13982
  import { confirm as confirm3, isCancel as isCancel3 } from "@clack/prompts";
12941
- import { existsSync as existsSync33, readFileSync as readFileSync29 } from "node:fs";
12942
- import path40 from "node:path";
13983
+ import { existsSync as existsSync36, readFileSync as readFileSync32 } from "node:fs";
13984
+ import path43 from "node:path";
12943
13985
  async function runGenerateRecorded(opts) {
12944
13986
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
12945
- const outDirAbs = path40.resolve(callerCwd, opts.out);
12946
- const recordedAsPath = path40.resolve(callerCwd, opts.recorded);
13987
+ const outDirAbs = path43.resolve(callerCwd, opts.out);
13988
+ const recordedAsPath = path43.resolve(callerCwd, opts.recorded);
12947
13989
  let task;
12948
13990
  let taskName;
12949
13991
  let authoredApi;
12950
13992
  let composition;
12951
- const isSet = existsSync33(path40.join(recordedAsPath, "recording-set.json"));
13993
+ const isSet = existsSync36(path43.join(recordedAsPath, "recording-set.json"));
12952
13994
  const registry = TASKS[opts.recorded];
12953
13995
  if (registry !== void 0 && !isSet) {
12954
13996
  task = registry;
@@ -12957,7 +13999,7 @@ async function runGenerateRecorded(opts) {
12957
13999
  try {
12958
14000
  const authored = authorTaskFromSet(recordedAsPath);
12959
14001
  task = authored.task;
12960
- taskName = path40.basename(recordedAsPath);
14002
+ taskName = path43.basename(recordedAsPath);
12961
14003
  authoredApi = authored.api;
12962
14004
  const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
12963
14005
  if (roles.success) composition = roles.data;
@@ -12991,7 +14033,7 @@ async function runGenerateRecorded(opts) {
12991
14033
  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)`);
12992
14034
  }
12993
14035
  const missing = task.configs.filter(
12994
- (c) => !existsSync33(path40.join(task.set, c.rep, "get_screenshot.json")) || !existsSync33(path40.join(task.set, c.rep, "get_metadata.json")) || !existsSync33(path40.join(task.set, c.rep, "get_design_context.json"))
14036
+ (c) => !existsSync36(path43.join(task.set, c.rep, "get_screenshot.json")) || !existsSync36(path43.join(task.set, c.rep, "get_metadata.json")) || !existsSync36(path43.join(task.set, c.rep, "get_design_context.json"))
12995
14037
  );
12996
14038
  if (missing.length > 0) {
12997
14039
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -13061,8 +14103,8 @@ async function runGenerateRecorded(opts) {
13061
14103
  ` : `${line}
13062
14104
  `);
13063
14105
  if (opts.dryRun) {
13064
- emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path40.join(outDirAbs, taskName) }, () => {
13065
- process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path40.join(outDirAbs, taskName)})
14106
+ emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path43.join(outDirAbs, taskName) }, () => {
14107
+ process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path43.join(outDirAbs, taskName)})
13066
14108
  `);
13067
14109
  });
13068
14110
  return;
@@ -13085,10 +14127,10 @@ async function runGenerateRecorded(opts) {
13085
14127
  });
13086
14128
  }
13087
14129
  }
13088
- const bundleDir = path40.join(outDirAbs, taskName);
13089
- if (existsSync33(path40.join(bundleDir, "component.json"))) {
14130
+ const bundleDir = path43.join(outDirAbs, taskName);
14131
+ if (existsSync36(path43.join(bundleDir, "component.json"))) {
13090
14132
  try {
13091
- const prior = readBundleManifest(readFileSync29(path40.join(bundleDir, "component.json"), "utf8")).manifest;
14133
+ const prior = readBundleManifest(readFileSync32(path43.join(bundleDir, "component.json"), "utf8")).manifest;
13092
14134
  if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
13093
14135
  fail(opts, ExitCode.InputValidation, {
13094
14136
  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`,
@@ -13254,7 +14296,7 @@ init_invocation();
13254
14296
  init_output();
13255
14297
  import { intro, isCancel, outro, password } from "@clack/prompts";
13256
14298
  import fs from "node:fs";
13257
- import path25 from "node:path";
14299
+ import path26 from "node:path";
13258
14300
  var INIT_DESCRIPTION = {
13259
14301
  name: "init",
13260
14302
  summary: "Configure Figma and OpenRouter credentials in .env (idempotent).",
@@ -13291,7 +14333,7 @@ async function runInit(flags) {
13291
14333
  printDescription(INIT_DESCRIPTION);
13292
14334
  return;
13293
14335
  }
13294
- const envPath = path25.resolve(process.cwd(), ".env");
14336
+ const envPath = path26.resolve(process.cwd(), ".env");
13295
14337
  const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
13296
14338
  let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
13297
14339
  let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
@@ -13312,7 +14354,7 @@ async function runInit(flags) {
13312
14354
  next.set(ENV_KEYS.figma, figmaToken);
13313
14355
  next.set(ENV_KEYS.openrouter, openrouterKey);
13314
14356
  const changed = existing.get(ENV_KEYS.figma) !== next.get(ENV_KEYS.figma) || existing.get(ENV_KEYS.openrouter) !== next.get(ENV_KEYS.openrouter);
13315
- const gitignorePath = path25.resolve(process.cwd(), ".gitignore");
14357
+ const gitignorePath = path26.resolve(process.cwd(), ".gitignore");
13316
14358
  const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
13317
14359
  const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
13318
14360
  if (flags.dryRun) {
@@ -13368,14 +14410,14 @@ init_invocation();
13368
14410
  init_output();
13369
14411
  init_entitlement();
13370
14412
  import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
13371
- import { readFileSync as readFileSync16, readdirSync as readdirSync5, existsSync as existsSync20 } from "node:fs";
14413
+ import { readFileSync as readFileSync17, readdirSync as readdirSync7, existsSync as existsSync21 } from "node:fs";
13372
14414
 
13373
14415
  // packages/cli/src/pipeline.ts
13374
14416
  init_src2();
13375
14417
  init_src4();
13376
14418
  init_src6();
13377
14419
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
13378
- import path26 from "node:path";
14420
+ import path27 from "node:path";
13379
14421
 
13380
14422
  // packages/cli/src/assets-module.ts
13381
14423
  init_src();
@@ -13711,7 +14753,7 @@ async function runGenerationPipeline(input) {
13711
14753
  });
13712
14754
  const written = [];
13713
14755
  if (!input.dryRun) {
13714
- const dir = path26.resolve(input.outDir, semantics.componentName);
14756
+ const dir = path27.resolve(input.outDir, semantics.componentName);
13715
14757
  mkdirSync5(dir, { recursive: true });
13716
14758
  const files = {
13717
14759
  // Bundle-local tokens: THE emission the component's CSS resolves
@@ -13735,13 +14777,13 @@ async function runGenerationPipeline(input) {
13735
14777
  `
13736
14778
  };
13737
14779
  for (const [name, content] of Object.entries(files)) {
13738
- const filePath = path26.join(dir, name);
14780
+ const filePath = path27.join(dir, name);
13739
14781
  writeFileSync8(filePath, content);
13740
14782
  written.push(filePath);
13741
14783
  }
13742
14784
  for (const artifact of emitTokenArtifacts(input.mapping)) {
13743
- const filePath = path26.resolve(input.outDir, artifact.path);
13744
- mkdirSync5(path26.dirname(filePath), { recursive: true });
14785
+ const filePath = path27.resolve(input.outDir, artifact.path);
14786
+ mkdirSync5(path27.dirname(filePath), { recursive: true });
13745
14787
  writeFileSync8(filePath, artifact.content);
13746
14788
  written.push(filePath);
13747
14789
  }
@@ -13800,7 +14842,7 @@ var GENERATE_DESCRIPTION = {
13800
14842
  function resolveProvidedSource(flags, contextFile) {
13801
14843
  let raw;
13802
14844
  try {
13803
- raw = readFileSync16(contextFile, "utf8");
14845
+ raw = readFileSync17(contextFile, "utf8");
13804
14846
  } catch {
13805
14847
  fail(flags, ExitCode.InputValidation, {
13806
14848
  error: `Cannot read context file "${contextFile}".`,
@@ -13920,11 +14962,11 @@ token mapping (${mapping.flat.length} variables):
13920
14962
  let initialCode;
13921
14963
  let initialSemantics;
13922
14964
  try {
13923
- if (existsSync20(flags.out)) {
13924
- for (const entry of readdirSync5(flags.out)) {
14965
+ if (existsSync21(flags.out)) {
14966
+ for (const entry of readdirSync7(flags.out)) {
13925
14967
  const cjPath = `${flags.out}/${entry}/component.json`;
13926
- if (!existsSync20(cjPath)) continue;
13927
- const cj = JSON.parse(readFileSync16(cjPath, "utf8"));
14968
+ if (!existsSync21(cjPath)) continue;
14969
+ const cj = JSON.parse(readFileSync17(cjPath, "utf8"));
13928
14970
  if (cj.name !== void 0 && Array.isArray(cj.props)) {
13929
14971
  previousApi = JSON.stringify({
13930
14972
  componentName: cj.name,
@@ -13932,14 +14974,14 @@ token mapping (${mapping.flat.length} variables):
13932
14974
  });
13933
14975
  const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
13934
14976
  const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
13935
- if (flags.refine && existsSync20(tsxPath) && existsSync20(cssPath)) {
14977
+ if (flags.refine && existsSync21(tsxPath) && existsSync21(cssPath)) {
13936
14978
  initialCode = {
13937
- tsx: readFileSync16(tsxPath, "utf8"),
13938
- css: readFileSync16(cssPath, "utf8")
14979
+ tsx: readFileSync17(tsxPath, "utf8"),
14980
+ css: readFileSync17(cssPath, "utf8")
13939
14981
  };
13940
14982
  const semPath = `${flags.out}/${entry}/semantics.json`;
13941
- if (existsSync20(semPath)) {
13942
- initialSemantics = JSON.parse(readFileSync16(semPath, "utf8"));
14983
+ if (existsSync21(semPath)) {
14984
+ initialSemantics = JSON.parse(readFileSync17(semPath, "utf8"));
13943
14985
  }
13944
14986
  }
13945
14987
  break;
@@ -14246,7 +15288,7 @@ function buildProgram() {
14246
15288
  runFontsRequired2({ ...flags, families, cacheDir: cmd.opts()["cache"] ?? DEFAULT_FONT_CACHE });
14247
15289
  });
14248
15290
  const engine = program.command("engine").description("Agent-harness protocol (ADR-012): the host agent proposes; these commands are the CLI-owned brief and oracle.");
14249
- engine.command("brief").argument("<taskOrSet>", "reference task name or recording-set directory").option("--bar <bar>", "target bar: pass (0.95) or cert (0.97)", "pass").option("--out <file>", "payload file path (default tendril-out/<name>-brief.md)").option("--model <id>", "declared proposer model (the MCP surface requires it; score refuses without one)").action(async (taskOrSet, _o, cmd) => {
15291
+ engine.command("brief").argument("<taskOrSet>", "reference task name or recording-set directory").option("--bar <bar>", "target bar: pass (0.95) or cert (0.97)", "pass").option("--out <file>", "payload file path (default tendril-out/<name>-brief.md)").option("--model <id>", "declared proposer model (the MCP surface requires it; score refuses without one)").option("--library <dir>", "workspace root holding confirmed partners generated bundles (ADR-013 composed pins; default: current directory)").action(async (taskOrSet, _o, cmd) => {
14250
15292
  const flags = globalFlags(cmd.parent.parent);
14251
15293
  const local = cmd.opts();
14252
15294
  const { runEngineBrief: runEngineBrief2 } = await Promise.resolve().then(() => (init_engine2(), engine_exports));
@@ -14255,10 +15297,11 @@ function buildProgram() {
14255
15297
  taskOrSet,
14256
15298
  bar: local["bar"] === "cert" ? "cert" : "pass",
14257
15299
  ...local["out"] !== void 0 ? { out: local["out"] } : {},
14258
- ...local["model"] !== void 0 ? { model: local["model"] } : {}
15300
+ ...local["model"] !== void 0 ? { model: local["model"] } : {},
15301
+ ...local["library"] !== void 0 ? { library: local["library"] } : {}
14259
15302
  });
14260
15303
  });
14261
- engine.command("score").argument("<taskOrSet>", "reference task name or recording-set directory").argument("<candidateDir>", "directory containing the proposed bundle files").option("--bar <bar>", "target bar: pass (0.95) or cert (0.97)", "pass").option("--host <name>", "self-reported host identity (recorded in provenance, labeled self-reported)").requiredOption("--model <id>", "proposer model, self-reported \u2014 required; a score without a declared model is not accepted").option("--rebind", "explicitly re-bind an already-bound bundle to a different recording set (refused otherwise \u2014 rebinding rewrites the bundle's verification identity)").action(async (taskOrSet, candidateDir, _o, cmd) => {
15304
+ engine.command("score").argument("<taskOrSet>", "reference task name or recording-set directory").argument("<candidateDir>", "directory containing the proposed bundle files").option("--bar <bar>", "target bar: pass (0.95) or cert (0.97)", "pass").option("--host <name>", "self-reported host identity (recorded in provenance, labeled self-reported)").requiredOption("--model <id>", "proposer model, self-reported \u2014 required; a score without a declared model is not accepted").option("--rebind", "explicitly re-bind an already-bound bundle to a different recording set (refused otherwise \u2014 rebinding rewrites the bundle's verification identity)").option("--library <dir>", "workspace root holding confirmed partners generated bundles (ADR-013 composed pins; default: current directory)").action(async (taskOrSet, candidateDir, _o, cmd) => {
14262
15305
  const flags = globalFlags(cmd.parent.parent);
14263
15306
  const local = cmd.opts();
14264
15307
  const { runEngineScore: runEngineScore2 } = await Promise.resolve().then(() => (init_engine2(), engine_exports));
@@ -14269,7 +15312,8 @@ function buildProgram() {
14269
15312
  bar: local["bar"] === "cert" ? "cert" : "pass",
14270
15313
  ...local["host"] !== void 0 ? { host: local["host"] } : {},
14271
15314
  model: local["model"],
14272
- rebind: local["rebind"]
15315
+ rebind: local["rebind"],
15316
+ ...local["library"] !== void 0 ? { library: local["library"] } : {}
14273
15317
  });
14274
15318
  });
14275
15319
  program.command("codeconnect").description("Emit a Figma Code Connect template (.figma.ts) for a certified bundle \u2014 every variant\u2192prop mapping from recorded truth, stamped with the trust statement. Publishing stays yours (figma connect publish; Org/Enterprise plan).").argument("<bundleDir>", "bundle directory (must carry component.json)").requiredOption("--figma-url <url>", "figma.com /design/ URL of the COMPONENT SET (Copy link to selection)").option("--set <dir>", "recording set override (default: the bundle's provenance path)").option("--out <file>", "output file (default: <bundle>/<Component>.figma.ts)").action(async (bundleDir, _o, cmd) => {
@@ -14297,6 +15341,19 @@ function buildProgram() {
14297
15341
  ...local["tendrilPrefix"] !== void 0 ? { tendrilPrefix: local["tendrilPrefix"] } : {}
14298
15342
  });
14299
15343
  });
15344
+ program.command("compose").description("Composition opportunities across a workspace's recording sets (ADR-013): --list discovers (read-only); --set persists a HUMAN's confirm/decline decisions into the host manifest. Nothing composes yet.").option("--list", "derive the index and report edges (default)").option("--library <dir...>", "workspace root(s) to scan for recording sets (default: current directory)").option("--set <dir>", "confirmation mode: propose this HOST set's id-backed pairs for a human decision").option("--confirm-compositions", "HUMAN-ONLY: confirm every open pair printed for --set (refused without an interactive terminal; agents surface the proposal instead)").option("--decline <pair-key...>", "HUMAN-ONLY: persistently decline the named pair(s) \u2014 asked once, never re-asked").action(async (_o, cmd) => {
15345
+ const flags = globalFlags(cmd.parent);
15346
+ const local = cmd.opts();
15347
+ const { runCompose: runCompose2 } = await Promise.resolve().then(() => (init_compose2(), compose_exports));
15348
+ runCompose2({
15349
+ ...flags,
15350
+ ...local["list"] !== void 0 ? { list: local["list"] } : {},
15351
+ ...local["library"] !== void 0 ? { library: local["library"] } : {},
15352
+ ...local["set"] !== void 0 ? { set: local["set"] } : {},
15353
+ ...local["confirmCompositions"] !== void 0 ? { confirmCompositions: local["confirmCompositions"] } : {},
15354
+ ...local["decline"] !== void 0 ? { decline: local["decline"] } : {}
15355
+ });
15356
+ });
14300
15357
  program.command("inspect").description("Build an eye-verifiable detail sheet from verify evidence: magnified recorded-vs-rendered crops of every small recorded node (icons, controls, marks) plus full-frame triples, in one static HTML page.").argument("<bundleDir>", "bundle directory with component.json and verify-evidence").option("--set <dir>", "recording set override (default: the bundle's provenance path)").option("--max-area <px2>", "node area ceiling for the detail sweep", "1024").action(async (bundleDir, _o, cmd) => {
14301
15358
  const flags = globalFlags(cmd.parent);
14302
15359
  const local = cmd.opts();
@@ -14308,7 +15365,7 @@ function buildProgram() {
14308
15365
  ...local["maxArea"] !== void 0 ? { maxArea: Number(local["maxArea"]) } : {}
14309
15366
  });
14310
15367
  });
14311
- program.command("verify").description("Recompute verification for a generated bundle against its recording set (per-config status, behaviors, honest exit codes).").argument("<bundleDir>", "bundle directory to verify").option("--task <name>", "legacy task adapter for bundles WITHOUT component.json (bundle v1 carries its own prop manifest)").option("--set <dir>", "recording set directory (overrides the bundle's provenance path)").option("--bar <bar>", "target bar: pass (0.95) or cert (0.97)", "pass").action(async (bundleDir, _opts, cmd) => {
15368
+ program.command("verify").description("Recompute verification for a generated bundle against its recording set (per-config status, behaviors, honest exit codes).").argument("<bundleDir>", "bundle directory to verify").option("--task <name>", "legacy task adapter for bundles WITHOUT component.json (bundle v1 carries its own prop manifest)").option("--set <dir>", "recording set directory (overrides the bundle's provenance path)").option("--bar <bar>", "target bar: pass (0.95) or cert (0.97)", "pass").option("--library <dir>", "workspace root holding confirmed partners generated bundles (ADR-013 composed pins; default: current directory)").action(async (bundleDir, _opts, cmd) => {
14312
15369
  const flags = globalFlags(cmd);
14313
15370
  const local = cmd.opts();
14314
15371
  const bar = local["bar"] === "cert" ? "cert" : "pass";
@@ -14318,6 +15375,7 @@ function buildProgram() {
14318
15375
  bundleDir,
14319
15376
  ...local["task"] !== void 0 ? { task: local["task"] } : {},
14320
15377
  ...local["set"] !== void 0 ? { set: local["set"] } : {},
15378
+ ...local["library"] !== void 0 ? { library: local["library"] } : {},
14321
15379
  bar
14322
15380
  });
14323
15381
  });