@tendrilapp/cli 0.1.42 → 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 +480 -291
- 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` });
|
|
@@ -6239,34 +6239,43 @@ function classifyBundleSurface(files, opts) {
|
|
|
6239
6239
|
const excluded = [];
|
|
6240
6240
|
const unknown = [];
|
|
6241
6241
|
for (const raw of files) {
|
|
6242
|
-
const
|
|
6243
|
-
const inEvidence =
|
|
6244
|
-
|
|
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;
|
|
6245
6254
|
if (name.includes("/")) {
|
|
6246
|
-
unknown.push(
|
|
6255
|
+
unknown.push(path56);
|
|
6247
6256
|
continue;
|
|
6248
6257
|
}
|
|
6249
6258
|
if (inEvidence) {
|
|
6250
|
-
if (name === "verify-report.json") published.push({ path:
|
|
6251
|
-
else if (name === "diff-legend.txt") published.push({ path:
|
|
6252
|
-
else if (name === "inspect.html") published.push({ path:
|
|
6253
|
-
else if (/-mount-failure\.png$/.test(name)) excluded.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)" });
|
|
6254
6263
|
else {
|
|
6255
6264
|
const hit = EVIDENCE_PATTERNS.find((p) => p.re.test(name));
|
|
6256
|
-
if (hit !== void 0) published.push({ path:
|
|
6257
|
-
else unknown.push(
|
|
6265
|
+
if (hit !== void 0) published.push({ path: path56, role: hit.role });
|
|
6266
|
+
else unknown.push(path56);
|
|
6258
6267
|
}
|
|
6259
6268
|
continue;
|
|
6260
6269
|
}
|
|
6261
|
-
if (name === opts.entry) published.push({ path:
|
|
6262
|
-
else if (name === "styles.css") published.push({ path:
|
|
6263
|
-
else if (name === "tokens.css") published.push({ path:
|
|
6264
|
-
else if (name === "fonts.css") published.push({ path:
|
|
6265
|
-
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" });
|
|
6266
6275
|
else {
|
|
6267
6276
|
const skip = EXCLUDED.find((e) => e.test(name));
|
|
6268
|
-
if (skip !== void 0) excluded.push({ path:
|
|
6269
|
-
else unknown.push(
|
|
6277
|
+
if (skip !== void 0) excluded.push({ path: path56, reason: skip.reason });
|
|
6278
|
+
else unknown.push(path56);
|
|
6270
6279
|
}
|
|
6271
6280
|
}
|
|
6272
6281
|
const roles = new Set(published.map((p) => p.role));
|
|
@@ -6276,8 +6285,8 @@ function missingInspectCrops(sheetText, publishedPaths) {
|
|
|
6276
6285
|
const held = new Set(publishedPaths);
|
|
6277
6286
|
const missing = /* @__PURE__ */ new Set();
|
|
6278
6287
|
for (const [, name] of sheetText.matchAll(/src="\.\/([^"]+)"/g)) {
|
|
6279
|
-
const
|
|
6280
|
-
if (!held.has(
|
|
6288
|
+
const path56 = `${EVIDENCE_DIR}/${name}`;
|
|
6289
|
+
if (!held.has(path56)) missing.add(path56);
|
|
6281
6290
|
}
|
|
6282
6291
|
return [...missing].sort();
|
|
6283
6292
|
}
|
|
@@ -6385,10 +6394,10 @@ function readScoredFiles(report) {
|
|
|
6385
6394
|
const entries = Object.entries(value);
|
|
6386
6395
|
if (entries.length === 0) return void 0;
|
|
6387
6396
|
const out = {};
|
|
6388
|
-
for (const [
|
|
6389
|
-
if (
|
|
6397
|
+
for (const [path56, digest] of entries) {
|
|
6398
|
+
if (path56 === "" || path56.startsWith("/") || path56.includes("..")) return void 0;
|
|
6390
6399
|
if (!isSetHash(digest)) return void 0;
|
|
6391
|
-
out[
|
|
6400
|
+
out[path56] = digest;
|
|
6392
6401
|
}
|
|
6393
6402
|
return out;
|
|
6394
6403
|
}
|
|
@@ -6396,11 +6405,11 @@ function compareScoredFiles(recorded, actual) {
|
|
|
6396
6405
|
const missing = [];
|
|
6397
6406
|
const unscored = [];
|
|
6398
6407
|
const changed = [];
|
|
6399
|
-
for (const [
|
|
6400
|
-
if (!(
|
|
6401
|
-
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);
|
|
6402
6411
|
}
|
|
6403
|
-
for (const
|
|
6412
|
+
for (const path56 of Object.keys(actual)) if (!(path56 in recorded)) unscored.push(path56);
|
|
6404
6413
|
return { missing: missing.sort(), unscored: unscored.sort(), changed: changed.sort() };
|
|
6405
6414
|
}
|
|
6406
6415
|
function scoredRecordingSetHash(report) {
|
|
@@ -8453,8 +8462,11 @@ var init_publish_client = __esm({
|
|
|
8453
8462
|
this.token = options.token;
|
|
8454
8463
|
this.send = options.fetch ?? globalThis.fetch;
|
|
8455
8464
|
}
|
|
8456
|
-
|
|
8457
|
-
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);
|
|
8458
8470
|
}
|
|
8459
8471
|
begin(input) {
|
|
8460
8472
|
return this.json("POST", "/api/publications", input, true);
|
|
@@ -8548,7 +8560,6 @@ var init_publish_client = __esm({
|
|
|
8548
8560
|
refusal: typeof record["refusal"] === "string" ? record["refusal"] : `the portal refused with ${String(response.status)}`,
|
|
8549
8561
|
...Array.isArray(record["detail"]) ? { detail: record["detail"] } : {},
|
|
8550
8562
|
...Array.isArray(record["missing"]) ? { missing: record["missing"] } : {},
|
|
8551
|
-
...isRecord(record["needsConsent"]) ? { needsConsent: record["needsConsent"] } : {},
|
|
8552
8563
|
...isRecord(record["needsConfirmation"]) ? { needsConfirmation: record["needsConfirmation"] } : {}
|
|
8553
8564
|
};
|
|
8554
8565
|
}
|
|
@@ -13791,6 +13802,72 @@ var init_activate = __esm({
|
|
|
13791
13802
|
}
|
|
13792
13803
|
});
|
|
13793
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
|
+
|
|
13794
13871
|
// packages/cli/src/commands/compose.ts
|
|
13795
13872
|
var compose_exports = {};
|
|
13796
13873
|
__export(compose_exports, {
|
|
@@ -13799,15 +13876,15 @@ __export(compose_exports, {
|
|
|
13799
13876
|
compositionPairsFor: () => compositionPairsFor,
|
|
13800
13877
|
runCompose: () => runCompose
|
|
13801
13878
|
});
|
|
13802
|
-
import { createHash as
|
|
13803
|
-
import { existsSync as
|
|
13804
|
-
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";
|
|
13805
13882
|
function compositionPairsFor(hostSet, roots) {
|
|
13806
|
-
const parent =
|
|
13883
|
+
const parent = path42.dirname(hostSet);
|
|
13807
13884
|
const explicitRoots = [...new Set(roots)];
|
|
13808
13885
|
let skippedParent;
|
|
13809
13886
|
let parentRoot = [];
|
|
13810
|
-
if (!explicitRoots.some((r) =>
|
|
13887
|
+
if (!explicitRoots.some((r) => path42.resolve(r) === path42.resolve(parent))) {
|
|
13811
13888
|
let parentEntries = 0;
|
|
13812
13889
|
try {
|
|
13813
13890
|
parentEntries = readdirSync14(parent).length;
|
|
@@ -13848,7 +13925,7 @@ function substitutionPairs(edges, hostSet) {
|
|
|
13848
13925
|
});
|
|
13849
13926
|
}
|
|
13850
13927
|
const pair = pairs.get(key);
|
|
13851
|
-
const poseDisplay = e.pose.reps.map((r) => `${
|
|
13928
|
+
const poseDisplay = e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ");
|
|
13852
13929
|
pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
|
|
13853
13930
|
for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
|
|
13854
13931
|
}
|
|
@@ -13860,7 +13937,7 @@ function runCompose(flags) {
|
|
|
13860
13937
|
return;
|
|
13861
13938
|
}
|
|
13862
13939
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
13863
|
-
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];
|
|
13864
13941
|
if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
|
|
13865
13942
|
fail(flags, ExitCode.InputValidation, {
|
|
13866
13943
|
error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
|
|
@@ -13869,7 +13946,7 @@ function runCompose(flags) {
|
|
|
13869
13946
|
});
|
|
13870
13947
|
}
|
|
13871
13948
|
if (flags.set !== void 0) {
|
|
13872
|
-
runComposeConfirm(flags,
|
|
13949
|
+
runComposeConfirm(flags, path42.resolve(base, flags.set), roots);
|
|
13873
13950
|
return;
|
|
13874
13951
|
}
|
|
13875
13952
|
const index = buildComposeIndex(roots);
|
|
@@ -13887,7 +13964,7 @@ function runCompose(flags) {
|
|
|
13887
13964
|
}
|
|
13888
13965
|
let lastHost = "";
|
|
13889
13966
|
for (const e of edges) {
|
|
13890
|
-
const host = `${
|
|
13967
|
+
const host = `${path42.basename(e.hostSet)}`;
|
|
13891
13968
|
if (host !== lastHost) {
|
|
13892
13969
|
process.stdout.write(`
|
|
13893
13970
|
${host}
|
|
@@ -13895,7 +13972,7 @@ ${host}
|
|
|
13895
13972
|
lastHost = host;
|
|
13896
13973
|
}
|
|
13897
13974
|
const partners = e.partners.map((p) => p.displayName).join(" / ") || "\u2014";
|
|
13898
|
-
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(", ")})` : "";
|
|
13899
13976
|
process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
|
|
13900
13977
|
`);
|
|
13901
13978
|
for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
|
|
@@ -13907,14 +13984,14 @@ ${NOTE}
|
|
|
13907
13984
|
});
|
|
13908
13985
|
}
|
|
13909
13986
|
function runComposeConfirm(flags, hostSet, roots) {
|
|
13910
|
-
if (!
|
|
13987
|
+
if (!existsSync33(path42.join(hostSet, "recording-set.json"))) {
|
|
13911
13988
|
fail(flags, ExitCode.InputValidation, {
|
|
13912
13989
|
error: `no recording-set.json in ${hostSet}`,
|
|
13913
13990
|
code: "no-recording-set",
|
|
13914
13991
|
remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
|
|
13915
13992
|
});
|
|
13916
13993
|
}
|
|
13917
|
-
const scanRoots = [.../* @__PURE__ */ new Set([...roots,
|
|
13994
|
+
const scanRoots = [.../* @__PURE__ */ new Set([...roots, path42.dirname(hostSet)])];
|
|
13918
13995
|
const index = buildComposeIndex(scanRoots);
|
|
13919
13996
|
const edges = composeReport(index);
|
|
13920
13997
|
const pairs = substitutionPairs(edges, hostSet);
|
|
@@ -13997,7 +14074,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
|
|
|
13997
14074
|
// full recording-set hash join lands with pin authoring, where
|
|
13998
14075
|
// task configs exist.)
|
|
13999
14076
|
manifestSha256: Object.fromEntries(
|
|
14000
|
-
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")])
|
|
14001
14078
|
)
|
|
14002
14079
|
},
|
|
14003
14080
|
// Schema shape ONLY (review: the display-side poseDisplay field
|
|
@@ -14077,10 +14154,10 @@ __export(record_exports, {
|
|
|
14077
14154
|
runRecordPlan: () => runRecordPlan,
|
|
14078
14155
|
runRecordStatus: () => runRecordStatus
|
|
14079
14156
|
});
|
|
14080
|
-
import { existsSync as
|
|
14157
|
+
import { existsSync as existsSync34, mkdtempSync as mkdtempSync2, readFileSync as readFileSync31, readdirSync as readdirSync15 } from "node:fs";
|
|
14081
14158
|
import os7 from "node:os";
|
|
14082
|
-
import
|
|
14083
|
-
import { writeFileSync as
|
|
14159
|
+
import path43 from "node:path";
|
|
14160
|
+
import { writeFileSync as writeFileSync15 } from "node:fs";
|
|
14084
14161
|
function recordsInteractionState(reports) {
|
|
14085
14162
|
const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
|
|
14086
14163
|
return reports.some((r) => evident(r.axis) || r.domain.some(evident));
|
|
@@ -14102,7 +14179,7 @@ function interactionDisclosure(component, reports) {
|
|
|
14102
14179
|
};
|
|
14103
14180
|
}
|
|
14104
14181
|
function symbolsFromMetadataEnvelope(file, sourceFrame) {
|
|
14105
|
-
const env = JSON.parse(
|
|
14182
|
+
const env = JSON.parse(readFileSync31(file, "utf8"));
|
|
14106
14183
|
const text = env.content.map((c) => c.text ?? "").join("\n");
|
|
14107
14184
|
const symbols = [];
|
|
14108
14185
|
const walk2 = (node, ancestor) => {
|
|
@@ -14160,8 +14237,8 @@ function runRecordPlan(opts) {
|
|
|
14160
14237
|
if (rawFile !== void 0) {
|
|
14161
14238
|
try {
|
|
14162
14239
|
const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
|
|
14163
|
-
const tmp =
|
|
14164
|
-
|
|
14240
|
+
const tmp = path43.join(mkdtempSync2(path43.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
|
|
14241
|
+
writeFileSync15(tmp, JSON.stringify(envelope));
|
|
14165
14242
|
metadataEntries.push({ file: tmp });
|
|
14166
14243
|
} catch (err) {
|
|
14167
14244
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -14182,7 +14259,7 @@ function runRecordPlan(opts) {
|
|
|
14182
14259
|
let metadataTruncated = false;
|
|
14183
14260
|
for (const { file, frame } of metadataEntries) {
|
|
14184
14261
|
try {
|
|
14185
|
-
const parsed = symbolsFromMetadataEnvelope(
|
|
14262
|
+
const parsed = symbolsFromMetadataEnvelope(path43.resolve(file), frame);
|
|
14186
14263
|
symbols.push(...parsed.symbols);
|
|
14187
14264
|
if (parsed.truncated) metadataTruncated = true;
|
|
14188
14265
|
} catch (err) {
|
|
@@ -14216,7 +14293,7 @@ function runRecordPlan(opts) {
|
|
|
14216
14293
|
if (symbols.length === 0) {
|
|
14217
14294
|
const leads = metadataEntries.flatMap(({ file }) => {
|
|
14218
14295
|
try {
|
|
14219
|
-
const env = JSON.parse(
|
|
14296
|
+
const env = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
|
|
14220
14297
|
return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
|
|
14221
14298
|
} catch {
|
|
14222
14299
|
return [];
|
|
@@ -14309,7 +14386,7 @@ function runRecordPlan(opts) {
|
|
|
14309
14386
|
// a CLI flag and the MCP surface has no parameter for it.
|
|
14310
14387
|
decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
|
|
14311
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.",
|
|
14312
|
-
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`]
|
|
14313
14390
|
},
|
|
14314
14391
|
{
|
|
14315
14392
|
id: "larger-allowance",
|
|
@@ -14319,6 +14396,7 @@ function runRecordPlan(opts) {
|
|
|
14319
14396
|
],
|
|
14320
14397
|
limitHintIfWhoamiIsSilent: LIMIT_HINT
|
|
14321
14398
|
};
|
|
14399
|
+
void reportRunPresence(opts.component, "recording");
|
|
14322
14400
|
emitData(
|
|
14323
14401
|
opts,
|
|
14324
14402
|
{
|
|
@@ -14507,7 +14585,7 @@ function runRecordNext(opts) {
|
|
|
14507
14585
|
const progress = payload["progress"];
|
|
14508
14586
|
process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
|
|
14509
14587
|
\u2192 ${payload["note"]}
|
|
14510
|
-
\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>`)}
|
|
14511
14589
|
`);
|
|
14512
14590
|
});
|
|
14513
14591
|
}
|
|
@@ -14581,7 +14659,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
14581
14659
|
const skipped = [];
|
|
14582
14660
|
const failed = [];
|
|
14583
14661
|
for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
|
|
14584
|
-
if (
|
|
14662
|
+
if (existsSync34(path43.join(setDir, rep, name))) {
|
|
14585
14663
|
skipped.push(name);
|
|
14586
14664
|
continue;
|
|
14587
14665
|
}
|
|
@@ -14603,16 +14681,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
14603
14681
|
}
|
|
14604
14682
|
function rawEnvelopeFromFile(file, parts) {
|
|
14605
14683
|
if (parts) {
|
|
14606
|
-
const blocks = JSON.parse(
|
|
14684
|
+
const blocks = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
|
|
14607
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");
|
|
14608
14686
|
return { content: blocks.map((text) => ({ type: "text", text })) };
|
|
14609
14687
|
}
|
|
14610
|
-
return { content: [{ type: "text", text:
|
|
14688
|
+
return { content: [{ type: "text", text: readFileSync31(path43.resolve(file), "utf8") }] };
|
|
14611
14689
|
}
|
|
14612
14690
|
async function runRecordIngest(opts) {
|
|
14613
14691
|
let payload;
|
|
14614
14692
|
try {
|
|
14615
|
-
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"));
|
|
14616
14694
|
} catch (err) {
|
|
14617
14695
|
fail(opts, ExitCode.InputValidation, {
|
|
14618
14696
|
error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -14624,7 +14702,7 @@ async function runRecordIngest(opts) {
|
|
|
14624
14702
|
fail(opts, ExitCode.InputValidation, {
|
|
14625
14703
|
error: "--raw is for text tool responses; screenshots are binary",
|
|
14626
14704
|
code: "envelope-invalid",
|
|
14627
|
-
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.`
|
|
14628
14706
|
});
|
|
14629
14707
|
}
|
|
14630
14708
|
if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
|
|
@@ -14644,7 +14722,7 @@ async function runRecordIngest(opts) {
|
|
|
14644
14722
|
remediation: REINGEST_GUIDANCE
|
|
14645
14723
|
});
|
|
14646
14724
|
}
|
|
14647
|
-
|
|
14725
|
+
writeFileSync15(path43.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
|
|
14648
14726
|
`);
|
|
14649
14727
|
emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
|
|
14650
14728
|
process.stdout.write("set-level get_variable_defs ingested\n");
|
|
@@ -14668,7 +14746,7 @@ async function runRecordIngest(opts) {
|
|
|
14668
14746
|
remediation: REINGEST_GUIDANCE
|
|
14669
14747
|
});
|
|
14670
14748
|
}
|
|
14671
|
-
|
|
14749
|
+
writeFileSync15(path43.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
|
|
14672
14750
|
`);
|
|
14673
14751
|
emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
|
|
14674
14752
|
process.stdout.write("set-level get_motion_context ingested\n");
|
|
@@ -14684,7 +14762,7 @@ async function runRecordIngest(opts) {
|
|
|
14684
14762
|
if (assets !== void 0) {
|
|
14685
14763
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
14686
14764
|
`);
|
|
14687
|
-
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>`)}
|
|
14688
14766
|
`);
|
|
14689
14767
|
}
|
|
14690
14768
|
});
|
|
@@ -14757,14 +14835,14 @@ async function runRecordIngestRep(opts) {
|
|
|
14757
14835
|
if (assets !== void 0) {
|
|
14758
14836
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
14759
14837
|
`);
|
|
14760
|
-
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>`)}
|
|
14761
14839
|
`);
|
|
14762
14840
|
}
|
|
14763
14841
|
});
|
|
14764
14842
|
}
|
|
14765
14843
|
function runRecordAsset(opts) {
|
|
14766
14844
|
if (opts.dir !== void 0) {
|
|
14767
|
-
const dir =
|
|
14845
|
+
const dir = path43.resolve(opts.dir);
|
|
14768
14846
|
const names = readdirSync15(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
|
|
14769
14847
|
if (names.length === 0) {
|
|
14770
14848
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -14776,7 +14854,7 @@ function runRecordAsset(opts) {
|
|
|
14776
14854
|
const ingested = [];
|
|
14777
14855
|
try {
|
|
14778
14856
|
for (const name of names) {
|
|
14779
|
-
ingestAsset(opts.setDir, opts.rep, name,
|
|
14857
|
+
ingestAsset(opts.setDir, opts.rep, name, readFileSync31(path43.join(dir, name)));
|
|
14780
14858
|
ingested.push(name);
|
|
14781
14859
|
}
|
|
14782
14860
|
} catch (err) {
|
|
@@ -14796,11 +14874,11 @@ function runRecordAsset(opts) {
|
|
|
14796
14874
|
fail(opts, ExitCode.InputValidation, {
|
|
14797
14875
|
error: "pass --name and --file for a single asset, or --dir for a batch",
|
|
14798
14876
|
code: "asset-rejected",
|
|
14799
|
-
remediation: tendrilCommand(`record asset --set ${
|
|
14877
|
+
remediation: tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
|
|
14800
14878
|
});
|
|
14801
14879
|
}
|
|
14802
14880
|
try {
|
|
14803
|
-
ingestAsset(opts.setDir, opts.rep, opts.name,
|
|
14881
|
+
ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync31(path43.resolve(opts.file)));
|
|
14804
14882
|
emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
|
|
14805
14883
|
process.stdout.write(`ingested ${opts.rep}/${opts.name}
|
|
14806
14884
|
`);
|
|
@@ -14817,8 +14895,8 @@ function runRecordStatus(opts) {
|
|
|
14817
14895
|
const status = sessionStatus(opts.setDir);
|
|
14818
14896
|
const composition = (() => {
|
|
14819
14897
|
try {
|
|
14820
|
-
const setDir =
|
|
14821
|
-
const { open, standing, invalid } = compositionPairsFor(setDir, [
|
|
14898
|
+
const setDir = path43.resolve(opts.setDir);
|
|
14899
|
+
const { open, standing, invalid } = compositionPairsFor(setDir, [path43.dirname(setDir)]);
|
|
14822
14900
|
return {
|
|
14823
14901
|
openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
|
|
14824
14902
|
confirmed: standing.filter((s) => s.status === "confirmed").length,
|
|
@@ -14839,7 +14917,7 @@ function runRecordStatus(opts) {
|
|
|
14839
14917
|
}
|
|
14840
14918
|
}
|
|
14841
14919
|
process.stdout.write(
|
|
14842
|
-
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\`
|
|
14843
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"
|
|
14844
14922
|
);
|
|
14845
14923
|
if ("unavailable" in composition) {
|
|
@@ -14847,7 +14925,7 @@ function runRecordStatus(opts) {
|
|
|
14847
14925
|
`);
|
|
14848
14926
|
} else if (composition.openPairs.length > 0) {
|
|
14849
14927
|
process.stdout.write(
|
|
14850
|
-
`COMPOSITION ${composition.openPairs.length} unconfirmed partner pair(s): ${composition.openPairs.map((p) => `${p.displayName} [pair-key ${p.pairKey}] (${p.instances} instance(s))`).join(", ")} \u2014 this set's recording references other recorded components. Before generating the HOST, record/generate the partner and have a human decide the pairing: \`${tendrilCommand(`compose --set ${
|
|
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.
|
|
14851
14929
|
`
|
|
14852
14930
|
);
|
|
14853
14931
|
} else if (composition.confirmed > 0) {
|
|
@@ -14887,7 +14965,7 @@ function narrowedRoles(derived, override) {
|
|
|
14887
14965
|
function rolesFromFile(opts, file, derived) {
|
|
14888
14966
|
let json;
|
|
14889
14967
|
try {
|
|
14890
|
-
json = JSON.parse(
|
|
14968
|
+
json = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
|
|
14891
14969
|
} catch (err) {
|
|
14892
14970
|
fail(opts, ExitCode.InputValidation, {
|
|
14893
14971
|
error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
@@ -14925,11 +15003,11 @@ function rolesFromFile(opts, file, derived) {
|
|
|
14925
15003
|
};
|
|
14926
15004
|
}
|
|
14927
15005
|
function runRecordFinish(opts) {
|
|
14928
|
-
if (!
|
|
15006
|
+
if (!existsSync34(path43.join(opts.setDir, "recording-set.json"))) {
|
|
14929
15007
|
fail(opts, ExitCode.InputValidation, {
|
|
14930
15008
|
error: `no recording-set.json in ${opts.setDir}`,
|
|
14931
15009
|
code: "no-recording-set",
|
|
14932
|
-
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.`
|
|
14933
15011
|
});
|
|
14934
15012
|
}
|
|
14935
15013
|
const { manifest, raw } = readManifestFile(opts.setDir);
|
|
@@ -14957,17 +15035,17 @@ function runRecordFinish(opts) {
|
|
|
14957
15035
|
fail(opts, ExitCode.ConfirmationRequired, {
|
|
14958
15036
|
error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
|
|
14959
15037
|
code: "roles-confirmation-not-interactive",
|
|
14960
|
-
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.`
|
|
14961
15039
|
});
|
|
14962
15040
|
}
|
|
14963
15041
|
const merged = { ...raw, roles };
|
|
14964
|
-
const { issues } = validateRecordingSet(merged, (rel) =>
|
|
15042
|
+
const { issues } = validateRecordingSet(merged, (rel) => existsSync34(path43.join(opts.setDir, rel)));
|
|
14965
15043
|
const errors = issues.filter((i) => i.severity === "error");
|
|
14966
15044
|
if (errors.length > 0) {
|
|
14967
15045
|
fail(opts, ExitCode.InputValidation, {
|
|
14968
15046
|
error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
|
|
14969
15047
|
code: "recording-set-invalid",
|
|
14970
|
-
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\`.`
|
|
14971
15049
|
});
|
|
14972
15050
|
}
|
|
14973
15051
|
for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
|
|
@@ -14986,6 +15064,7 @@ var init_record = __esm({
|
|
|
14986
15064
|
init_src();
|
|
14987
15065
|
init_src4();
|
|
14988
15066
|
init_output();
|
|
15067
|
+
init_run_presence();
|
|
14989
15068
|
init_entitlement();
|
|
14990
15069
|
init_invocation();
|
|
14991
15070
|
init_compose2();
|
|
@@ -15017,9 +15096,9 @@ var init_record = __esm({
|
|
|
15017
15096
|
});
|
|
15018
15097
|
|
|
15019
15098
|
// packages/cli/src/font-guidance.ts
|
|
15020
|
-
import
|
|
15099
|
+
import path44 from "node:path";
|
|
15021
15100
|
function fontsUnprovenRemediation(setDir) {
|
|
15022
|
-
const set = setDir === void 0 ? void 0 :
|
|
15101
|
+
const set = setDir === void 0 ? void 0 : path44.resolve(setDir);
|
|
15023
15102
|
if (set !== void 0) {
|
|
15024
15103
|
try {
|
|
15025
15104
|
const needs = recordedFontNeeds(set);
|
|
@@ -15094,8 +15173,8 @@ __export(fonts_exports, {
|
|
|
15094
15173
|
runFontsResolveSet: () => runFontsResolveSet,
|
|
15095
15174
|
runFontsStatus: () => runFontsStatus
|
|
15096
15175
|
});
|
|
15097
|
-
import { existsSync as
|
|
15098
|
-
import
|
|
15176
|
+
import { existsSync as existsSync35, readFileSync as readFileSync32 } from "node:fs";
|
|
15177
|
+
import path45 from "node:path";
|
|
15099
15178
|
async function runFontsResolve(opts) {
|
|
15100
15179
|
const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
|
|
15101
15180
|
const queryRefusal = systemFaceRefusal(opts.family.trim());
|
|
@@ -15116,7 +15195,7 @@ async function runFontsResolve(opts) {
|
|
|
15116
15195
|
}
|
|
15117
15196
|
}
|
|
15118
15197
|
async function runFontsResolveSet(opts) {
|
|
15119
|
-
const setDir =
|
|
15198
|
+
const setDir = path45.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
|
|
15120
15199
|
let needs = [];
|
|
15121
15200
|
try {
|
|
15122
15201
|
needs = recordedFontNeeds(setDir);
|
|
@@ -15211,16 +15290,16 @@ async function runFontsResolveSet(opts) {
|
|
|
15211
15290
|
}
|
|
15212
15291
|
}
|
|
15213
15292
|
function runFontsStatus(opts) {
|
|
15214
|
-
const manifestPath2 =
|
|
15215
|
-
if (!
|
|
15293
|
+
const manifestPath2 = path45.join(opts.cacheDir, "manifest.json");
|
|
15294
|
+
if (!existsSync35(manifestPath2)) {
|
|
15216
15295
|
fail(opts, ExitCode.FontsUnproven, {
|
|
15217
15296
|
error: `no font cache at ${opts.cacheDir}`,
|
|
15218
15297
|
code: "fonts-unresolved",
|
|
15219
15298
|
remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
|
|
15220
15299
|
});
|
|
15221
15300
|
}
|
|
15222
|
-
const faces = JSON.parse(
|
|
15223
|
-
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;
|
|
15224
15303
|
emitData(opts, { faces, lockVerdicts }, () => {
|
|
15225
15304
|
for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
|
|
15226
15305
|
`);
|
|
@@ -15264,13 +15343,13 @@ function familyMismatch(family, declared) {
|
|
|
15264
15343
|
}
|
|
15265
15344
|
function runFontsAdd(opts) {
|
|
15266
15345
|
if (opts.set !== void 0) {
|
|
15267
|
-
const declared = taskFontFamilies(
|
|
15346
|
+
const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
|
|
15268
15347
|
const mismatch = familyMismatch(opts.family, declared);
|
|
15269
15348
|
if (mismatch !== void 0) {
|
|
15270
15349
|
fail(opts, ExitCode.InputValidation, {
|
|
15271
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.`,
|
|
15272
15351
|
code: "font-family-not-declared",
|
|
15273
|
-
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.`
|
|
15274
15353
|
});
|
|
15275
15354
|
}
|
|
15276
15355
|
} else {
|
|
@@ -15329,13 +15408,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
|
|
|
15329
15408
|
}
|
|
15330
15409
|
function runFontsAddSystem(opts) {
|
|
15331
15410
|
if (opts.set !== void 0) {
|
|
15332
|
-
const declared = taskFontFamilies(
|
|
15411
|
+
const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
|
|
15333
15412
|
const mismatch = familyMismatch(opts.family, declared);
|
|
15334
15413
|
if (mismatch !== void 0) {
|
|
15335
15414
|
fail(opts, ExitCode.InputValidation, {
|
|
15336
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.`,
|
|
15337
15416
|
code: "font-family-not-declared",
|
|
15338
|
-
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.`
|
|
15339
15418
|
});
|
|
15340
15419
|
}
|
|
15341
15420
|
} else {
|
|
@@ -15385,12 +15464,12 @@ var init_fonts = __esm({
|
|
|
15385
15464
|
});
|
|
15386
15465
|
|
|
15387
15466
|
// packages/cli/src/profile-input.ts
|
|
15388
|
-
import { existsSync as
|
|
15389
|
-
import
|
|
15467
|
+
import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
|
|
15468
|
+
import path46 from "node:path";
|
|
15390
15469
|
function loadCodebaseProfile(flags, profilePath) {
|
|
15391
15470
|
if (profilePath === void 0) return null;
|
|
15392
|
-
const abs =
|
|
15393
|
-
if (!
|
|
15471
|
+
const abs = path46.resolve(profilePath);
|
|
15472
|
+
if (!existsSync36(abs)) {
|
|
15394
15473
|
fail(flags, ExitCode.InputValidation, {
|
|
15395
15474
|
error: `no profile at ${abs}`,
|
|
15396
15475
|
code: "profile_missing",
|
|
@@ -15398,7 +15477,7 @@ function loadCodebaseProfile(flags, profilePath) {
|
|
|
15398
15477
|
});
|
|
15399
15478
|
}
|
|
15400
15479
|
try {
|
|
15401
|
-
return readCodebaseProfile(
|
|
15480
|
+
return readCodebaseProfile(readFileSync33(abs, "utf8"));
|
|
15402
15481
|
} catch (error) {
|
|
15403
15482
|
fail(flags, ExitCode.InputValidation, {
|
|
15404
15483
|
error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -15440,8 +15519,8 @@ __export(verify_exports, {
|
|
|
15440
15519
|
runVerify: () => runVerify,
|
|
15441
15520
|
verdictCaveatsFor: () => verdictCaveatsFor
|
|
15442
15521
|
});
|
|
15443
|
-
import { existsSync as
|
|
15444
|
-
import
|
|
15522
|
+
import { existsSync as existsSync37, readFileSync as readFileSync34, rmSync as rmSync6, writeFileSync as writeFileSync16 } from "node:fs";
|
|
15523
|
+
import path47 from "node:path";
|
|
15445
15524
|
function interactionCoverage(behaviors) {
|
|
15446
15525
|
const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
|
|
15447
15526
|
const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
|
|
@@ -15682,12 +15761,12 @@ function compositionReport(input) {
|
|
|
15682
15761
|
function eyeCheck(bundleDir) {
|
|
15683
15762
|
return {
|
|
15684
15763
|
command: tendrilCommand(`inspect "${bundleDir}"`),
|
|
15685
|
-
sheetPath:
|
|
15764
|
+
sheetPath: path47.join(bundleDir, "verify-evidence", "inspect.html"),
|
|
15686
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."
|
|
15687
15766
|
};
|
|
15688
15767
|
}
|
|
15689
15768
|
function evidenceArtifacts(evidenceDir, reps) {
|
|
15690
|
-
const named = (name) =>
|
|
15769
|
+
const named = (name) => existsSync37(path47.join(evidenceDir, name)) ? name : null;
|
|
15691
15770
|
return {
|
|
15692
15771
|
legend: named("diff-legend.txt"),
|
|
15693
15772
|
configs: reps.map((rep) => {
|
|
@@ -15735,7 +15814,7 @@ function taskFromManifest(opts, manifest, setDir) {
|
|
|
15735
15814
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
15736
15815
|
const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
15737
15816
|
const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
|
|
15738
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
15817
|
+
const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
|
|
15739
15818
|
const authored = (() => {
|
|
15740
15819
|
if (registry !== void 0) return void 0;
|
|
15741
15820
|
try {
|
|
@@ -15796,19 +15875,19 @@ function verdictCaveatsFor(input) {
|
|
|
15796
15875
|
async function runVerify(opts) {
|
|
15797
15876
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
15798
15877
|
let recordingSetDrift;
|
|
15799
|
-
const setOverride = opts.set !== void 0 ?
|
|
15800
|
-
opts = { ...opts, bundleDir:
|
|
15801
|
-
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)) {
|
|
15802
15881
|
fail(opts, ExitCode.InputValidation, {
|
|
15803
15882
|
error: `bundle directory not found: ${opts.bundleDir}`,
|
|
15804
15883
|
code: "bundle-missing",
|
|
15805
15884
|
remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
|
|
15806
15885
|
});
|
|
15807
15886
|
}
|
|
15808
|
-
const manifestPath2 =
|
|
15887
|
+
const manifestPath2 = path47.join(opts.bundleDir, "component.json");
|
|
15809
15888
|
let manifest;
|
|
15810
|
-
if (
|
|
15811
|
-
const { manifest: parsed, issues } = readBundleManifest(
|
|
15889
|
+
if (existsSync37(manifestPath2)) {
|
|
15890
|
+
const { manifest: parsed, issues } = readBundleManifest(readFileSync34(manifestPath2, "utf8"));
|
|
15812
15891
|
if (issues.length > 0) {
|
|
15813
15892
|
fail(opts, ExitCode.InputValidation, {
|
|
15814
15893
|
error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
|
|
@@ -15839,21 +15918,21 @@ async function runVerify(opts) {
|
|
|
15839
15918
|
task = registry;
|
|
15840
15919
|
} else if (manifest !== void 0) {
|
|
15841
15920
|
const resolveSetDir = (p) => {
|
|
15842
|
-
if (
|
|
15843
|
-
const fromRepo =
|
|
15844
|
-
if (
|
|
15845
|
-
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);
|
|
15846
15925
|
};
|
|
15847
15926
|
const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
|
|
15848
|
-
if (!
|
|
15927
|
+
if (!existsSync37(path47.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path47.resolve(t.set) === path47.resolve(setDir))) {
|
|
15849
15928
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
15850
15929
|
error: `recording set not found or unmanifested: ${setDir}`,
|
|
15851
15930
|
code: "recording-set-missing",
|
|
15852
15931
|
remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
|
|
15853
15932
|
});
|
|
15854
15933
|
}
|
|
15855
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
15856
|
-
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"))) {
|
|
15857
15936
|
const recordedSlugs = registry.configs.map((c) => c.rep);
|
|
15858
15937
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
15859
15938
|
unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
@@ -15885,9 +15964,9 @@ async function runVerify(opts) {
|
|
|
15885
15964
|
recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
|
|
15886
15965
|
}
|
|
15887
15966
|
for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
|
|
15888
|
-
const p =
|
|
15889
|
-
if (!
|
|
15890
|
-
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)));
|
|
15891
15970
|
if (issues.length > 0) {
|
|
15892
15971
|
fail(opts, ExitCode.InputValidation, {
|
|
15893
15972
|
error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
|
|
@@ -15925,7 +16004,7 @@ async function runVerify(opts) {
|
|
|
15925
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)`);
|
|
15926
16005
|
}
|
|
15927
16006
|
const missing = task.configs.filter(
|
|
15928
|
-
(c) => !
|
|
16007
|
+
(c) => !existsSync37(path47.join(task.set, c.rep, "get_screenshot.json")) || !existsSync37(path47.join(task.set, c.rep, "get_metadata.json"))
|
|
15929
16008
|
);
|
|
15930
16009
|
if (missing.length > 0) {
|
|
15931
16010
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
@@ -15935,8 +16014,8 @@ async function runVerify(opts) {
|
|
|
15935
16014
|
});
|
|
15936
16015
|
}
|
|
15937
16016
|
const bar = BARS2[opts.bar];
|
|
15938
|
-
const evidenceDir =
|
|
15939
|
-
|
|
16017
|
+
const evidenceDir = path47.join(opts.bundleDir, "verify-evidence");
|
|
16018
|
+
rmSync6(evidenceDir, { recursive: true, force: true });
|
|
15940
16019
|
const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
15941
16020
|
const quality = await checkBundleQuality(
|
|
15942
16021
|
opts.bundleDir,
|
|
@@ -15953,7 +16032,7 @@ async function runVerify(opts) {
|
|
|
15953
16032
|
// ASKED, never "follows every convention".
|
|
15954
16033
|
loadCodebaseProfile(opts, opts.profile) ?? void 0
|
|
15955
16034
|
);
|
|
15956
|
-
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");
|
|
15957
16036
|
const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
|
|
15958
16037
|
const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs, {
|
|
15959
16038
|
...opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {},
|
|
@@ -15974,10 +16053,10 @@ async function runVerify(opts) {
|
|
|
15974
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.`);
|
|
15975
16054
|
}
|
|
15976
16055
|
const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
15977
|
-
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: [] };
|
|
15978
16057
|
const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
|
|
15979
16058
|
const renderExpectations = verifyPins.pins.map((pin) => ({
|
|
15980
|
-
modulePath:
|
|
16059
|
+
modulePath: path47.join(composedModuleDir(pin.partnerName), pin.entryComponent),
|
|
15981
16060
|
component: pin.entryComponent,
|
|
15982
16061
|
stampName: `${pin.pairKey}:${pin.entryComponent}`,
|
|
15983
16062
|
hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
|
|
@@ -16270,7 +16349,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
16270
16349
|
}
|
|
16271
16350
|
process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
|
|
16272
16351
|
`);
|
|
16273
|
-
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")));
|
|
16274
16353
|
if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
|
|
16275
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)
|
|
16276
16355
|
`);
|
|
@@ -16325,7 +16404,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
16325
16404
|
persistReport(opts, report, evidenceDir);
|
|
16326
16405
|
}
|
|
16327
16406
|
function persistReport(opts, report, evidenceDir) {
|
|
16328
|
-
if (!
|
|
16407
|
+
if (!existsSync37(evidenceDir)) return;
|
|
16329
16408
|
const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
|
|
16330
16409
|
const withExit = {
|
|
16331
16410
|
...report,
|
|
@@ -16333,9 +16412,9 @@ function persistReport(opts, report, evidenceDir) {
|
|
|
16333
16412
|
...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
|
|
16334
16413
|
};
|
|
16335
16414
|
try {
|
|
16336
|
-
|
|
16337
|
-
|
|
16338
|
-
`${JSON.stringify(verifyReportForTransport(withExit,
|
|
16415
|
+
writeFileSync16(
|
|
16416
|
+
path47.join(evidenceDir, VERIFY_REPORT_FILENAME),
|
|
16417
|
+
`${JSON.stringify(verifyReportForTransport(withExit, path47.basename(opts.bundleDir)), null, 2)}
|
|
16339
16418
|
`
|
|
16340
16419
|
);
|
|
16341
16420
|
} catch (e) {
|
|
@@ -16385,11 +16464,11 @@ __export(engine_exports, {
|
|
|
16385
16464
|
runEngineBrief: () => runEngineBrief,
|
|
16386
16465
|
runEngineScore: () => runEngineScore
|
|
16387
16466
|
});
|
|
16388
|
-
import { appendFileSync, existsSync as
|
|
16389
|
-
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";
|
|
16390
16469
|
function resolveEngineTask(opts, callerCwd) {
|
|
16391
|
-
const asPath =
|
|
16392
|
-
const isSet =
|
|
16470
|
+
const asPath = path48.resolve(callerCwd, opts.taskOrSet);
|
|
16471
|
+
const isSet = existsSync38(path48.join(asPath, "recording-set.json"));
|
|
16393
16472
|
const registry = TASKS[opts.taskOrSet];
|
|
16394
16473
|
if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
|
|
16395
16474
|
if (isSet) {
|
|
@@ -16398,7 +16477,7 @@ function resolveEngineTask(opts, callerCwd) {
|
|
|
16398
16477
|
for (const d of authored.disclosures) warn(opts, d);
|
|
16399
16478
|
return {
|
|
16400
16479
|
task: authored.task,
|
|
16401
|
-
name:
|
|
16480
|
+
name: path48.basename(asPath),
|
|
16402
16481
|
ref: asPath,
|
|
16403
16482
|
disclosures: authored.disclosures,
|
|
16404
16483
|
interactionEvidence: authored.api.interactionEvidence,
|
|
@@ -16425,10 +16504,11 @@ function runEngineBrief(opts) {
|
|
|
16425
16504
|
requireEntitlement(opts);
|
|
16426
16505
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16427
16506
|
const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
|
|
16507
|
+
void reportRunPresence(name, "implementing");
|
|
16428
16508
|
const bar = BARS3[opts.bar];
|
|
16429
|
-
if (
|
|
16509
|
+
if (existsSync38(path48.join(task.set, "recording-set.json"))) {
|
|
16430
16510
|
try {
|
|
16431
|
-
const { open, skippedParent } = compositionPairsFor(
|
|
16511
|
+
const { open, skippedParent } = compositionPairsFor(path48.resolve(task.set), [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
|
|
16432
16512
|
if (skippedParent !== void 0) {
|
|
16433
16513
|
disclosures.push(
|
|
16434
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.`
|
|
@@ -16437,7 +16517,7 @@ function runEngineBrief(opts) {
|
|
|
16437
16517
|
if (open.length > 0) {
|
|
16438
16518
|
const componentNames = [...new Set(open.map((p) => p.displayName))];
|
|
16439
16519
|
disclosures.push(
|
|
16440
|
-
`COMPOSITION PAIRS UNDECIDED: this recording references ${componentNames.length} other recorded component(s) (${open.map((p) => `${p.displayName} [pair-key ${p.key}]: ${p.instances.length} instance(s)`).join("; ")}) and no human has decided the pairing. This brief treats the component as SELF-CONTAINED \u2014 you will re-implement the partner's pixels locally. If composition is wanted: generate the partner bundle first, have a human run \`${tendrilCommand(`compose --set ${
|
|
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.`
|
|
16441
16521
|
);
|
|
16442
16522
|
}
|
|
16443
16523
|
} catch (err) {
|
|
@@ -16454,9 +16534,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
16454
16534
|
const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
|
|
16455
16535
|
const segments = buildSegments(task, "files");
|
|
16456
16536
|
let notRecorded;
|
|
16457
|
-
const manifestPath2 =
|
|
16458
|
-
if (
|
|
16459
|
-
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;
|
|
16460
16540
|
}
|
|
16461
16541
|
const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
|
|
16462
16542
|
|
|
@@ -16464,7 +16544,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
16464
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.
|
|
16465
16545
|
${notRecorded}` : "";
|
|
16466
16546
|
let fontProvisioning;
|
|
16467
|
-
if (
|
|
16547
|
+
if (existsSync38(manifestPath2)) {
|
|
16468
16548
|
const missingFams = unprovisionedFamilies(task.set);
|
|
16469
16549
|
const unprovided = unprovisionedFaces(task.set);
|
|
16470
16550
|
const weightOnly = missingFams.length === 0;
|
|
@@ -16486,7 +16566,7 @@ ${notRecorded}` : "";
|
|
|
16486
16566
|
};
|
|
16487
16567
|
}
|
|
16488
16568
|
}
|
|
16489
|
-
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]);
|
|
16490
16570
|
const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
|
|
16491
16571
|
const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
|
|
16492
16572
|
|
|
@@ -16522,10 +16602,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
|
|
|
16522
16602
|
|
|
16523
16603
|
=== TASK PAYLOAD (recorded truth, verbatim) ===
|
|
16524
16604
|
${segments}`;
|
|
16525
|
-
const payloadFile =
|
|
16526
|
-
const candidateDirSuggestion =
|
|
16527
|
-
|
|
16528
|
-
|
|
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);
|
|
16529
16609
|
emitData(
|
|
16530
16610
|
opts,
|
|
16531
16611
|
{
|
|
@@ -16571,7 +16651,7 @@ ${segments}`;
|
|
|
16571
16651
|
// command must search the same bundle roots the pins came
|
|
16572
16652
|
// from, or the oracle and the brief describe different worlds.
|
|
16573
16653
|
`Run \`${tendrilCommand(
|
|
16574
|
-
`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`
|
|
16575
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.`,
|
|
16576
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).",
|
|
16577
16657
|
"Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
|
|
@@ -16586,8 +16666,8 @@ ${segments}`;
|
|
|
16586
16666
|
);
|
|
16587
16667
|
}
|
|
16588
16668
|
function appendScoreHistory(candidateDir, entry) {
|
|
16589
|
-
const file =
|
|
16590
|
-
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;
|
|
16591
16671
|
const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
|
|
16592
16672
|
appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
|
|
16593
16673
|
`);
|
|
@@ -16595,9 +16675,10 @@ function appendScoreHistory(candidateDir, entry) {
|
|
|
16595
16675
|
async function runEngineScore(opts) {
|
|
16596
16676
|
requireEntitlement(opts);
|
|
16597
16677
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16598
|
-
const candidateDir =
|
|
16678
|
+
const candidateDir = path48.resolve(callerCwd, opts.candidateDir);
|
|
16599
16679
|
const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
|
|
16600
|
-
|
|
16680
|
+
void reportRunPresence(name, "implementing");
|
|
16681
|
+
if (!existsSync38(candidateDir)) {
|
|
16601
16682
|
fail(opts, ExitCode.InputValidation, {
|
|
16602
16683
|
error: `candidate directory not found: ${candidateDir}`,
|
|
16603
16684
|
code: "candidate-missing",
|
|
@@ -16622,10 +16703,10 @@ async function runEngineScore(opts) {
|
|
|
16622
16703
|
for (const g of missingWeights(task.set)) {
|
|
16623
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)`);
|
|
16624
16705
|
}
|
|
16625
|
-
if (opts.rebind !== true &&
|
|
16706
|
+
if (opts.rebind !== true && existsSync38(path48.join(candidateDir, "component.json"))) {
|
|
16626
16707
|
const prior = (() => {
|
|
16627
16708
|
try {
|
|
16628
|
-
const read = readBundleManifest(
|
|
16709
|
+
const read = readBundleManifest(readFileSync35(path48.join(candidateDir, "component.json"), "utf8"));
|
|
16629
16710
|
return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
|
|
16630
16711
|
} catch {
|
|
16631
16712
|
return { unreadable: true };
|
|
@@ -16647,13 +16728,13 @@ async function runEngineScore(opts) {
|
|
|
16647
16728
|
}
|
|
16648
16729
|
}
|
|
16649
16730
|
const bar = BARS3[opts.bar];
|
|
16650
|
-
const evidenceDir =
|
|
16731
|
+
const evidenceDir = path48.join(candidateDir, "verify-evidence");
|
|
16651
16732
|
const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
16652
16733
|
const hoverOpts = opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {};
|
|
16653
16734
|
const parity = await checkHoverParity(task, candidateDir, task.configs, { ...hoverOpts, failureShotDir: evidenceDir });
|
|
16654
16735
|
const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
|
|
16655
16736
|
const occlusionFailures = occlusionRows.filter((o) => !o.pass);
|
|
16656
|
-
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]);
|
|
16657
16738
|
const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
|
|
16658
16739
|
const behaviors = [...await checkBehaviors(task, candidateDir, hoverOpts), ...parity, ...composition];
|
|
16659
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)";
|
|
@@ -16858,6 +16939,7 @@ var init_engine2 = __esm({
|
|
|
16858
16939
|
init_entitlement();
|
|
16859
16940
|
init_verify();
|
|
16860
16941
|
init_compose2();
|
|
16942
|
+
init_run_presence();
|
|
16861
16943
|
init_src5();
|
|
16862
16944
|
BARS3 = {
|
|
16863
16945
|
pass: { sim: 0.95, ink: 0.95 },
|
|
@@ -16871,11 +16953,11 @@ var codeconnect_exports = {};
|
|
|
16871
16953
|
__export(codeconnect_exports, {
|
|
16872
16954
|
runCodeConnect: () => runCodeConnect
|
|
16873
16955
|
});
|
|
16874
|
-
import { existsSync as
|
|
16875
|
-
import
|
|
16956
|
+
import { existsSync as existsSync39, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
|
|
16957
|
+
import path49 from "node:path";
|
|
16876
16958
|
function runCodeConnect(opts) {
|
|
16877
16959
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16878
|
-
const bundleDir =
|
|
16960
|
+
const bundleDir = path49.resolve(callerCwd, opts.bundleDir);
|
|
16879
16961
|
let url;
|
|
16880
16962
|
try {
|
|
16881
16963
|
url = new URL(opts.figmaUrl);
|
|
@@ -16891,7 +16973,7 @@ function runCodeConnect(opts) {
|
|
|
16891
16973
|
}
|
|
16892
16974
|
let manifest;
|
|
16893
16975
|
try {
|
|
16894
|
-
const read = readBundleManifest(
|
|
16976
|
+
const read = readBundleManifest(readFileSync36(path49.join(bundleDir, "component.json"), "utf8"));
|
|
16895
16977
|
if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
|
|
16896
16978
|
manifest = read.manifest;
|
|
16897
16979
|
} catch (err) {
|
|
@@ -16901,8 +16983,8 @@ function runCodeConnect(opts) {
|
|
|
16901
16983
|
remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
|
|
16902
16984
|
});
|
|
16903
16985
|
}
|
|
16904
|
-
const setDir =
|
|
16905
|
-
if (!
|
|
16986
|
+
const setDir = path49.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
|
|
16987
|
+
if (!existsSync39(path49.join(setDir, "recording-set.json"))) {
|
|
16906
16988
|
fail(opts, ExitCode.InputValidation, {
|
|
16907
16989
|
error: `recording set not found at ${setDir}`,
|
|
16908
16990
|
code: "codeconnect-no-set",
|
|
@@ -16923,10 +17005,10 @@ function runCodeConnect(opts) {
|
|
|
16923
17005
|
const component = api.component;
|
|
16924
17006
|
const recManifest = loadManifest(setDir);
|
|
16925
17007
|
const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
|
|
16926
|
-
const meta =
|
|
16927
|
-
if (!
|
|
17008
|
+
const meta = path49.join(setDir, r.slug, "get_metadata.json");
|
|
17009
|
+
if (!existsSync39(meta)) return void 0;
|
|
16928
17010
|
try {
|
|
16929
|
-
return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(
|
|
17011
|
+
return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync36(meta, "utf8"))))?.[1];
|
|
16930
17012
|
} catch {
|
|
16931
17013
|
return void 0;
|
|
16932
17014
|
}
|
|
@@ -16991,7 +17073,7 @@ function runCodeConnect(opts) {
|
|
|
16991
17073
|
axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
|
|
16992
17074
|
fragmentVars.push(varName);
|
|
16993
17075
|
}
|
|
16994
|
-
const entryRel =
|
|
17076
|
+
const entryRel = path49.relative(callerCwd, path49.join(bundleDir, manifest.entry));
|
|
16995
17077
|
const trust = manifest.trustStatement.split("\n")[0] ?? "";
|
|
16996
17078
|
const lines = [
|
|
16997
17079
|
`// url=${opts.figmaUrl}`,
|
|
@@ -17015,8 +17097,8 @@ function runCodeConnect(opts) {
|
|
|
17015
17097
|
`}`,
|
|
17016
17098
|
``
|
|
17017
17099
|
].join("\n");
|
|
17018
|
-
const outFile =
|
|
17019
|
-
|
|
17100
|
+
const outFile = path49.resolve(callerCwd, opts.out ?? path49.join(bundleDir, `${component}.figma.ts`));
|
|
17101
|
+
writeFileSync18(outFile, lines);
|
|
17020
17102
|
emitData(
|
|
17021
17103
|
opts,
|
|
17022
17104
|
{
|
|
@@ -17054,18 +17136,18 @@ var init_codeconnect = __esm({
|
|
|
17054
17136
|
});
|
|
17055
17137
|
|
|
17056
17138
|
// packages/mcp/src/server.ts
|
|
17057
|
-
import { createHash as
|
|
17058
|
-
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";
|
|
17059
17141
|
import os8 from "node:os";
|
|
17060
|
-
import
|
|
17142
|
+
import path50 from "node:path";
|
|
17061
17143
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
17062
17144
|
import { z as z14 } from "zod";
|
|
17063
17145
|
function sourceHash() {
|
|
17064
|
-
const dir =
|
|
17065
|
-
const h =
|
|
17146
|
+
const dir = path50.dirname(fileURLToPath6(import.meta.url));
|
|
17147
|
+
const h = createHash10("sha256");
|
|
17066
17148
|
for (const f of readdirSync16(dir).filter((n) => n.endsWith(".ts")).sort()) {
|
|
17067
17149
|
h.update(f);
|
|
17068
|
-
h.update(
|
|
17150
|
+
h.update(readFileSync37(path50.join(dir, f)));
|
|
17069
17151
|
}
|
|
17070
17152
|
return h.digest("hex").slice(0, 16);
|
|
17071
17153
|
}
|
|
@@ -17073,10 +17155,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
|
|
|
17073
17155
|
var init_server = __esm({
|
|
17074
17156
|
"packages/mcp/src/server.ts"() {
|
|
17075
17157
|
"use strict";
|
|
17076
|
-
REPO_ROOT3 =
|
|
17077
|
-
CLI_BIN =
|
|
17078
|
-
BUNDLED_CLI =
|
|
17079
|
-
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] };
|
|
17080
17162
|
str = (d) => z14.string().describe(d);
|
|
17081
17163
|
optStr = (d) => z14.string().optional().describe(d);
|
|
17082
17164
|
TOOLS = [
|
|
@@ -17107,13 +17189,13 @@ var init_server = __esm({
|
|
|
17107
17189
|
const single = i["metadata"];
|
|
17108
17190
|
const parts = i["metadataParts"];
|
|
17109
17191
|
if (single !== void 0 || parts !== void 0) {
|
|
17110
|
-
const tmp =
|
|
17192
|
+
const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17111
17193
|
if (single !== void 0) {
|
|
17112
|
-
|
|
17194
|
+
writeFileSync19(tmp, single);
|
|
17113
17195
|
argvOut.push("--metadata-raw-file", tmp);
|
|
17114
17196
|
} else {
|
|
17115
17197
|
if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
|
|
17116
|
-
|
|
17198
|
+
writeFileSync19(tmp, JSON.stringify(parts));
|
|
17117
17199
|
argvOut.push("--metadata-raw-parts-file", tmp);
|
|
17118
17200
|
}
|
|
17119
17201
|
}
|
|
@@ -17163,6 +17245,24 @@ var init_server = __esm({
|
|
|
17163
17245
|
schema: z14.object({}),
|
|
17164
17246
|
argv: () => ["login", "--device-wait"]
|
|
17165
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
|
+
},
|
|
17166
17266
|
{
|
|
17167
17267
|
name: "tendril_record_next",
|
|
17168
17268
|
annotations: { readOnlyHint: true },
|
|
@@ -17204,14 +17304,14 @@ var init_server = __esm({
|
|
|
17204
17304
|
const bridge = (label, single, parts) => {
|
|
17205
17305
|
if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
|
|
17206
17306
|
if (single === void 0 && parts === void 0) return;
|
|
17207
|
-
const tmp =
|
|
17307
|
+
const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17208
17308
|
if (single !== void 0) {
|
|
17209
|
-
|
|
17309
|
+
writeFileSync19(tmp, single);
|
|
17210
17310
|
argvOut.push(`--${label}-file`, tmp);
|
|
17211
17311
|
} else {
|
|
17212
17312
|
const blocks = parts;
|
|
17213
17313
|
if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
|
|
17214
|
-
|
|
17314
|
+
writeFileSync19(tmp, JSON.stringify(blocks));
|
|
17215
17315
|
argvOut.push(`--${label}-parts-file`, tmp);
|
|
17216
17316
|
}
|
|
17217
17317
|
};
|
|
@@ -17252,12 +17352,12 @@ var init_server = __esm({
|
|
|
17252
17352
|
const file = i["file"];
|
|
17253
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)");
|
|
17254
17354
|
if (file !== void 0) return [...base, "--file", file];
|
|
17255
|
-
const tmp =
|
|
17355
|
+
const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17256
17356
|
if (text !== void 0) {
|
|
17257
|
-
|
|
17357
|
+
writeFileSync19(tmp, text);
|
|
17258
17358
|
return [...base, "--file", tmp, "--raw"];
|
|
17259
17359
|
}
|
|
17260
|
-
|
|
17360
|
+
writeFileSync19(tmp, JSON.stringify(texts));
|
|
17261
17361
|
return [...base, "--file", tmp, "--raw-parts"];
|
|
17262
17362
|
}
|
|
17263
17363
|
},
|
|
@@ -17414,13 +17514,13 @@ __export(permissions_exports, {
|
|
|
17414
17514
|
runPermissions: () => runPermissions,
|
|
17415
17515
|
writeSelection: () => writeSelection
|
|
17416
17516
|
});
|
|
17417
|
-
import { existsSync as
|
|
17517
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync12, readFileSync as readFileSync38, writeFileSync as writeFileSync20 } from "node:fs";
|
|
17418
17518
|
import os9 from "node:os";
|
|
17419
|
-
import
|
|
17519
|
+
import path51 from "node:path";
|
|
17420
17520
|
function mergeAllowlist(file, entries, denyEntries = []) {
|
|
17421
17521
|
let settings = {};
|
|
17422
|
-
if (
|
|
17423
|
-
settings = JSON.parse(
|
|
17522
|
+
if (existsSync41(file) && readFileSync38(file, "utf8").trim() !== "") {
|
|
17523
|
+
settings = JSON.parse(readFileSync38(file, "utf8"));
|
|
17424
17524
|
if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
|
|
17425
17525
|
}
|
|
17426
17526
|
const permissions = settings["permissions"] ??= {};
|
|
@@ -17440,8 +17540,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
|
|
|
17440
17540
|
}
|
|
17441
17541
|
if (added.length > 0 || denyAdded.length > 0) {
|
|
17442
17542
|
allow.push(...added);
|
|
17443
|
-
|
|
17444
|
-
|
|
17543
|
+
mkdirSync12(path51.dirname(file), { recursive: true });
|
|
17544
|
+
writeFileSync20(file, `${JSON.stringify(settings, null, 2)}
|
|
17445
17545
|
`);
|
|
17446
17546
|
}
|
|
17447
17547
|
return { added, alreadyPresent, denyAdded };
|
|
@@ -17505,7 +17605,7 @@ async function runPermissions(flags) {
|
|
|
17505
17605
|
}
|
|
17506
17606
|
if (flags.write) {
|
|
17507
17607
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
17508
|
-
const file = flags.user ?
|
|
17608
|
+
const file = flags.user ? path51.join(os9.homedir(), ".claude", "settings.json") : path51.join(base, ".claude", "settings.local.json");
|
|
17509
17609
|
const { entries, denyEntries } = writeSelection(result, flags.user === true);
|
|
17510
17610
|
if (flags.dryRun) {
|
|
17511
17611
|
emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
|
|
@@ -17654,13 +17754,13 @@ __export(inspect_exports, {
|
|
|
17654
17754
|
INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
|
|
17655
17755
|
runInspect: () => runInspect
|
|
17656
17756
|
});
|
|
17657
|
-
import { existsSync as
|
|
17658
|
-
import
|
|
17757
|
+
import { existsSync as existsSync42, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "node:fs";
|
|
17758
|
+
import path52 from "node:path";
|
|
17659
17759
|
function readVerifyReport(evidenceDir) {
|
|
17660
|
-
const p =
|
|
17661
|
-
if (!
|
|
17760
|
+
const p = path52.join(evidenceDir, VERIFY_REPORT_FILENAME);
|
|
17761
|
+
if (!existsSync42(p)) return void 0;
|
|
17662
17762
|
try {
|
|
17663
|
-
return JSON.parse(
|
|
17763
|
+
return JSON.parse(readFileSync39(p, "utf8"));
|
|
17664
17764
|
} catch {
|
|
17665
17765
|
return void 0;
|
|
17666
17766
|
}
|
|
@@ -17688,17 +17788,17 @@ async function runInspect(opts) {
|
|
|
17688
17788
|
printDescription(INSPECT_DESCRIPTION);
|
|
17689
17789
|
return;
|
|
17690
17790
|
}
|
|
17691
|
-
const bundleDir =
|
|
17692
|
-
const evidenceDir =
|
|
17693
|
-
const manifestPath2 =
|
|
17694
|
-
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)) {
|
|
17695
17795
|
fail(opts, ExitCode.InputValidation, {
|
|
17696
|
-
error: `nothing to inspect in ${bundleDir} \u2014 ${
|
|
17796
|
+
error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync42(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
|
|
17697
17797
|
code: "no-evidence",
|
|
17698
17798
|
remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
|
|
17699
17799
|
});
|
|
17700
17800
|
}
|
|
17701
|
-
const { manifest } = readBundleManifest(
|
|
17801
|
+
const { manifest } = readBundleManifest(readFileSync39(manifestPath2, "utf8"));
|
|
17702
17802
|
if (manifest === void 0) {
|
|
17703
17803
|
fail(opts, ExitCode.InputValidation, {
|
|
17704
17804
|
error: "component.json did not parse as a bundle manifest",
|
|
@@ -17706,9 +17806,9 @@ async function runInspect(opts) {
|
|
|
17706
17806
|
remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
|
|
17707
17807
|
});
|
|
17708
17808
|
}
|
|
17709
|
-
const setDir =
|
|
17809
|
+
const setDir = path52.resolve(opts.set ?? manifest.provenance.recordingSet.path);
|
|
17710
17810
|
const report = readVerifyReport(evidenceDir);
|
|
17711
|
-
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`)));
|
|
17712
17812
|
if (reps.length === 0) {
|
|
17713
17813
|
fail(opts, ExitCode.InputValidation, {
|
|
17714
17814
|
error: "verify-evidence holds no ref/render pairs for this bundle's configs",
|
|
@@ -17719,15 +17819,15 @@ async function runInspect(opts) {
|
|
|
17719
17819
|
let crops = 0;
|
|
17720
17820
|
const sections = [];
|
|
17721
17821
|
for (const rep of reps) {
|
|
17722
|
-
const ref = new Uint8Array(
|
|
17723
|
-
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`)));
|
|
17724
17824
|
const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
|
|
17725
17825
|
const cells = [];
|
|
17726
17826
|
for (const [i, n] of nodes.entries()) {
|
|
17727
17827
|
const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
|
|
17728
17828
|
try {
|
|
17729
|
-
|
|
17730
|
-
|
|
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));
|
|
17731
17831
|
} catch {
|
|
17732
17832
|
continue;
|
|
17733
17833
|
}
|
|
@@ -17744,8 +17844,8 @@ async function runInspect(opts) {
|
|
|
17744
17844
|
if (reps.includes(c.rep)) continue;
|
|
17745
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>`);
|
|
17746
17846
|
}
|
|
17747
|
-
const sheet =
|
|
17748
|
-
|
|
17847
|
+
const sheet = path52.join(evidenceDir, "inspect.html");
|
|
17848
|
+
writeFileSync21(
|
|
17749
17849
|
sheet,
|
|
17750
17850
|
`<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
|
|
17751
17851
|
body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
|
|
@@ -17818,8 +17918,8 @@ __export(login_exports, {
|
|
|
17818
17918
|
runLogout: () => runLogout
|
|
17819
17919
|
});
|
|
17820
17920
|
import { spawn } from "node:child_process";
|
|
17821
|
-
import { existsSync as
|
|
17822
|
-
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";
|
|
17823
17923
|
import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
|
|
17824
17924
|
async function runLogin(opts, deps) {
|
|
17825
17925
|
const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
|
|
@@ -17913,13 +18013,13 @@ function settleDecision(opts, origin, outcome) {
|
|
|
17913
18013
|
}
|
|
17914
18014
|
}
|
|
17915
18015
|
function pendingLoginPath() {
|
|
17916
|
-
return
|
|
18016
|
+
return path53.join(path53.dirname(sessionPath()), "pending-login.json");
|
|
17917
18017
|
}
|
|
17918
18018
|
async function deviceStartPhase(opts, origin, deps) {
|
|
17919
18019
|
const started = await startHandshake(opts, origin, deps);
|
|
17920
18020
|
const file = pendingLoginPath();
|
|
17921
|
-
|
|
17922
|
-
|
|
18021
|
+
mkdirSync13(path53.dirname(file), { recursive: true });
|
|
18022
|
+
writeFileSync22(file, `${JSON.stringify({ origin, ...started }, null, 2)}
|
|
17923
18023
|
`, { mode: 384 });
|
|
17924
18024
|
deps.openBrowser(started.verificationUrl);
|
|
17925
18025
|
emitData(
|
|
@@ -17944,9 +18044,9 @@ async function deviceStartPhase(opts, origin, deps) {
|
|
|
17944
18044
|
async function deviceWaitPhase(opts, deps) {
|
|
17945
18045
|
const file = pendingLoginPath();
|
|
17946
18046
|
let pending;
|
|
17947
|
-
if (
|
|
18047
|
+
if (existsSync43(file)) {
|
|
17948
18048
|
try {
|
|
17949
|
-
const parsed = JSON.parse(
|
|
18049
|
+
const parsed = JSON.parse(readFileSync40(file, "utf8"));
|
|
17950
18050
|
if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
|
|
17951
18051
|
pending = parsed;
|
|
17952
18052
|
}
|
|
@@ -17960,7 +18060,7 @@ async function deviceWaitPhase(opts, deps) {
|
|
|
17960
18060
|
remediation: `Start one first: ${tendrilCommand("login --device-start")} (the tendril_login tool).`
|
|
17961
18061
|
});
|
|
17962
18062
|
}
|
|
17963
|
-
const done = () =>
|
|
18063
|
+
const done = () => rmSync7(file, { force: true });
|
|
17964
18064
|
const total = Math.ceil(660 / Math.max(1, pending.intervalSeconds));
|
|
17965
18065
|
let ticks = 0;
|
|
17966
18066
|
const outcome = await waitForDecision(opts, pending.origin, deps, pending, () => {
|
|
@@ -18197,10 +18297,10 @@ var publish_exports = {};
|
|
|
18197
18297
|
__export(publish_exports, {
|
|
18198
18298
|
runPublish: () => runPublish
|
|
18199
18299
|
});
|
|
18200
|
-
import { existsSync as
|
|
18201
|
-
import
|
|
18300
|
+
import { existsSync as existsSync44, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync23 } from "node:fs";
|
|
18301
|
+
import path54 from "node:path";
|
|
18202
18302
|
async function runPublish(opts) {
|
|
18203
|
-
const bundleDir =
|
|
18303
|
+
const bundleDir = path54.resolve(opts.bundleDir);
|
|
18204
18304
|
const bundle = readBundle(opts, bundleDir);
|
|
18205
18305
|
const report = bundle.report;
|
|
18206
18306
|
if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
|
|
@@ -18245,7 +18345,7 @@ async function runPublish(opts) {
|
|
|
18245
18345
|
const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
|
|
18246
18346
|
if (sheetEntry !== void 0) {
|
|
18247
18347
|
const missingCrops = missingInspectCrops(
|
|
18248
|
-
|
|
18348
|
+
readFileSync41(path54.join(bundleDir, sheetEntry.path), "utf8"),
|
|
18249
18349
|
surface.published.map((p) => p.path)
|
|
18250
18350
|
);
|
|
18251
18351
|
if (missingCrops.length > 0) {
|
|
@@ -18312,46 +18412,40 @@ async function runPublish(opts) {
|
|
|
18312
18412
|
}
|
|
18313
18413
|
const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
|
|
18314
18414
|
const client = opts.client ?? httpClient(opts, origin);
|
|
18315
|
-
if (opts.
|
|
18316
|
-
|
|
18317
|
-
error: "--confirm-publish requires an interactive terminal \u2014 putting a component on the internet is a human-only decision",
|
|
18318
|
-
code: "publish-confirmation-not-interactive",
|
|
18319
|
-
remediation: `A human runs \`${tendrilCommand(`publish ${opts.bundleDir} --confirm-publish`)}\` in their own terminal. Agents: show this to your operator instead of confirming it.`
|
|
18320
|
-
});
|
|
18321
|
-
}
|
|
18322
|
-
if (opts.acceptTerms === true) {
|
|
18323
|
-
const accepted = await client.acceptTerms({ figmaFile });
|
|
18324
|
-
if (!accepted.ok) refuse2(opts, accepted, "accept-terms-refused");
|
|
18415
|
+
if (opts.approveWait === true) {
|
|
18416
|
+
await approveWaitPhase(opts, client, bundleDir);
|
|
18325
18417
|
}
|
|
18326
|
-
|
|
18418
|
+
void reportRunPresence(componentName, "publishing");
|
|
18419
|
+
let opened = await client.begin({
|
|
18327
18420
|
componentName,
|
|
18328
18421
|
figmaFile,
|
|
18329
18422
|
entry: bundle.manifest.entry,
|
|
18330
18423
|
files: bundle.files,
|
|
18331
|
-
report: bundle.reportText
|
|
18332
|
-
confirmed: opts.confirmPublish === true
|
|
18424
|
+
report: bundle.reportText
|
|
18333
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
|
+
}
|
|
18334
18435
|
if (!opened.ok) {
|
|
18335
|
-
if (opened.needsConsent !== void 0) {
|
|
18336
|
-
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18337
|
-
error: `publishing this design system needs an acceptance of the publishing terms (version ${opened.needsConsent.termsVersion}) first`,
|
|
18338
|
-
code: "publishing-terms-not-accepted",
|
|
18339
|
-
remediation: `Read the terms, then run \`${tendrilCommand(`publish ${opts.bundleDir} --accept-terms --confirm-publish`)}\`. The acceptance covers this design system only.`
|
|
18340
|
-
});
|
|
18341
|
-
}
|
|
18342
18436
|
if (opened.needsConfirmation !== void 0) {
|
|
18343
18437
|
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18344
18438
|
error: `publishing ${JSON.stringify(opened.needsConfirmation.componentName)} for the first time is a decision a person makes, not a build step`,
|
|
18345
18439
|
code: "first-publish-unconfirmed",
|
|
18346
|
-
remediation:
|
|
18440
|
+
remediation: "Approve it in your browser \u2014 run the publish again and open the link it prints."
|
|
18347
18441
|
});
|
|
18348
18442
|
}
|
|
18349
18443
|
refuse2(opts, opened, "publish-refused");
|
|
18350
18444
|
}
|
|
18351
18445
|
const uploaded = [];
|
|
18352
18446
|
for (const object of opened.value.plan.objects) {
|
|
18353
|
-
const file =
|
|
18354
|
-
if (!
|
|
18447
|
+
const file = path54.join(bundleDir, object.relPath);
|
|
18448
|
+
if (!existsSync44(file)) {
|
|
18355
18449
|
fail(opts, ExitCode.InputValidation, {
|
|
18356
18450
|
error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
|
|
18357
18451
|
code: "planned-file-missing",
|
|
@@ -18361,12 +18455,15 @@ async function runPublish(opts) {
|
|
|
18361
18455
|
const sent = await client.upload({
|
|
18362
18456
|
publicationId: opened.value.publicationId,
|
|
18363
18457
|
relPath: object.relPath,
|
|
18364
|
-
bytes: new Uint8Array(
|
|
18458
|
+
bytes: new Uint8Array(readFileSync41(file))
|
|
18365
18459
|
});
|
|
18366
18460
|
if (!sent.ok) refuse2(opts, sent, "upload-refused");
|
|
18367
18461
|
uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
|
|
18368
18462
|
}
|
|
18369
18463
|
const committed = await client.commit({ publicationId: opened.value.publicationId });
|
|
18464
|
+
if (committed.ok) {
|
|
18465
|
+
await endRunPresence(componentName);
|
|
18466
|
+
}
|
|
18370
18467
|
if (!committed.ok) {
|
|
18371
18468
|
if (committed.missing !== void 0 && committed.missing.length > 0) {
|
|
18372
18469
|
fail(opts, ExitCode.General, {
|
|
@@ -18403,23 +18500,23 @@ async function runPublish(opts) {
|
|
|
18403
18500
|
);
|
|
18404
18501
|
}
|
|
18405
18502
|
function readBundle(opts, bundleDir) {
|
|
18406
|
-
const manifestPath2 =
|
|
18407
|
-
const reportPath =
|
|
18408
|
-
if (!
|
|
18503
|
+
const manifestPath2 = path54.join(bundleDir, "component.json");
|
|
18504
|
+
const reportPath = path54.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
|
|
18505
|
+
if (!existsSync44(manifestPath2)) {
|
|
18409
18506
|
fail(opts, ExitCode.InputValidation, {
|
|
18410
18507
|
error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
|
|
18411
18508
|
code: "not-a-bundle",
|
|
18412
18509
|
remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
|
|
18413
18510
|
});
|
|
18414
18511
|
}
|
|
18415
|
-
if (!
|
|
18512
|
+
if (!existsSync44(reportPath)) {
|
|
18416
18513
|
fail(opts, ExitCode.InputValidation, {
|
|
18417
18514
|
error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
|
|
18418
18515
|
code: "bundle-not-verified",
|
|
18419
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.`
|
|
18420
18517
|
});
|
|
18421
18518
|
}
|
|
18422
|
-
const { manifest } = readBundleManifest(
|
|
18519
|
+
const { manifest } = readBundleManifest(readFileSync41(manifestPath2, "utf8"));
|
|
18423
18520
|
if (manifest === void 0) {
|
|
18424
18521
|
fail(opts, ExitCode.InputValidation, {
|
|
18425
18522
|
error: "component.json did not parse as a bundle manifest",
|
|
@@ -18427,7 +18524,7 @@ function readBundle(opts, bundleDir) {
|
|
|
18427
18524
|
remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
|
|
18428
18525
|
});
|
|
18429
18526
|
}
|
|
18430
|
-
const reportText =
|
|
18527
|
+
const reportText = readFileSync41(reportPath, "utf8");
|
|
18431
18528
|
let report;
|
|
18432
18529
|
try {
|
|
18433
18530
|
report = JSON.parse(reportText);
|
|
@@ -18487,6 +18584,96 @@ function refuse2(opts, sent, code) {
|
|
|
18487
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.`
|
|
18488
18585
|
});
|
|
18489
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;
|
|
18490
18677
|
var init_publish = __esm({
|
|
18491
18678
|
"packages/cli/src/commands/publish.ts"() {
|
|
18492
18679
|
"use strict";
|
|
@@ -18495,6 +18682,8 @@ var init_publish = __esm({
|
|
|
18495
18682
|
init_invocation();
|
|
18496
18683
|
init_output();
|
|
18497
18684
|
init_publish_client();
|
|
18685
|
+
init_run_presence();
|
|
18686
|
+
APPROVAL_WAIT_CAP_MS = 31 * 6e4;
|
|
18498
18687
|
}
|
|
18499
18688
|
});
|
|
18500
18689
|
|
|
@@ -18522,17 +18711,17 @@ __export(generate_recorded_exports, {
|
|
|
18522
18711
|
runGenerateRecorded: () => runGenerateRecorded
|
|
18523
18712
|
});
|
|
18524
18713
|
import { confirm as confirm3, isCancel as isCancel4 } from "@clack/prompts";
|
|
18525
|
-
import { existsSync as
|
|
18526
|
-
import
|
|
18714
|
+
import { existsSync as existsSync45, readFileSync as readFileSync42 } from "node:fs";
|
|
18715
|
+
import path55 from "node:path";
|
|
18527
18716
|
async function runGenerateRecorded(opts) {
|
|
18528
18717
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
18529
|
-
const outDirAbs =
|
|
18530
|
-
const recordedAsPath =
|
|
18718
|
+
const outDirAbs = path55.resolve(callerCwd, opts.out);
|
|
18719
|
+
const recordedAsPath = path55.resolve(callerCwd, opts.recorded);
|
|
18531
18720
|
let task;
|
|
18532
18721
|
let taskName;
|
|
18533
18722
|
let authoredApi;
|
|
18534
18723
|
let composition;
|
|
18535
|
-
const isSet =
|
|
18724
|
+
const isSet = existsSync45(path55.join(recordedAsPath, "recording-set.json"));
|
|
18536
18725
|
const registry = TASKS[opts.recorded];
|
|
18537
18726
|
if (registry !== void 0 && !isSet) {
|
|
18538
18727
|
task = registry;
|
|
@@ -18541,7 +18730,7 @@ async function runGenerateRecorded(opts) {
|
|
|
18541
18730
|
try {
|
|
18542
18731
|
const authored = authorTaskFromSet(recordedAsPath);
|
|
18543
18732
|
task = authored.task;
|
|
18544
|
-
taskName =
|
|
18733
|
+
taskName = path55.basename(recordedAsPath);
|
|
18545
18734
|
authoredApi = authored.api;
|
|
18546
18735
|
const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
|
|
18547
18736
|
if (roles.success) composition = roles.data;
|
|
@@ -18575,7 +18764,7 @@ async function runGenerateRecorded(opts) {
|
|
|
18575
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)`);
|
|
18576
18765
|
}
|
|
18577
18766
|
const missing = task.configs.filter(
|
|
18578
|
-
(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"))
|
|
18579
18768
|
);
|
|
18580
18769
|
if (missing.length > 0) {
|
|
18581
18770
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
@@ -18645,8 +18834,8 @@ async function runGenerateRecorded(opts) {
|
|
|
18645
18834
|
` : `${line}
|
|
18646
18835
|
`);
|
|
18647
18836
|
if (opts.dryRun) {
|
|
18648
|
-
emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite:
|
|
18649
|
-
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)})
|
|
18650
18839
|
`);
|
|
18651
18840
|
});
|
|
18652
18841
|
return;
|
|
@@ -18669,10 +18858,10 @@ async function runGenerateRecorded(opts) {
|
|
|
18669
18858
|
});
|
|
18670
18859
|
}
|
|
18671
18860
|
}
|
|
18672
|
-
const bundleDir =
|
|
18673
|
-
if (
|
|
18861
|
+
const bundleDir = path55.join(outDirAbs, taskName);
|
|
18862
|
+
if (existsSync45(path55.join(bundleDir, "component.json"))) {
|
|
18674
18863
|
try {
|
|
18675
|
-
const prior = readBundleManifest(
|
|
18864
|
+
const prior = readBundleManifest(readFileSync42(path55.join(bundleDir, "component.json"), "utf8")).manifest;
|
|
18676
18865
|
if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
|
|
18677
18866
|
fail(opts, ExitCode.InputValidation, {
|
|
18678
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`,
|
|
@@ -19965,7 +20154,7 @@ function buildProgram() {
|
|
|
19965
20154
|
...local["revoke"] !== void 0 ? { revoke: local["revoke"] } : {}
|
|
19966
20155
|
});
|
|
19967
20156
|
});
|
|
19968
|
-
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) => {
|
|
19969
20158
|
const flags = globalFlags(cmd.parent);
|
|
19970
20159
|
const local = cmd.opts();
|
|
19971
20160
|
const { runPublish: runPublish2 } = await Promise.resolve().then(() => (init_publish(), publish_exports));
|
|
@@ -19974,8 +20163,8 @@ function buildProgram() {
|
|
|
19974
20163
|
bundleDir,
|
|
19975
20164
|
...local["to"] !== void 0 ? { to: local["to"] } : {},
|
|
19976
20165
|
...local["name"] !== void 0 ? { name: local["name"] } : {},
|
|
19977
|
-
...local["
|
|
19978
|
-
...local["
|
|
20166
|
+
...local["approveStart"] !== void 0 ? { approveStart: local["approveStart"] } : {},
|
|
20167
|
+
...local["approveWait"] !== void 0 ? { approveWait: local["approveWait"] } : {}
|
|
19979
20168
|
});
|
|
19980
20169
|
});
|
|
19981
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) => {
|