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