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