@tendrilapp/cli 0.1.42 → 0.1.44
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 +510 -297
- 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,14 @@ 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);
|
|
8470
|
+
}
|
|
8471
|
+
whoami() {
|
|
8472
|
+
return this.json("GET", "/api/whoami", null, true);
|
|
8458
8473
|
}
|
|
8459
8474
|
begin(input) {
|
|
8460
8475
|
return this.json("POST", "/api/publications", input, true);
|
|
@@ -8548,7 +8563,6 @@ var init_publish_client = __esm({
|
|
|
8548
8563
|
refusal: typeof record["refusal"] === "string" ? record["refusal"] : `the portal refused with ${String(response.status)}`,
|
|
8549
8564
|
...Array.isArray(record["detail"]) ? { detail: record["detail"] } : {},
|
|
8550
8565
|
...Array.isArray(record["missing"]) ? { missing: record["missing"] } : {},
|
|
8551
|
-
...isRecord(record["needsConsent"]) ? { needsConsent: record["needsConsent"] } : {},
|
|
8552
8566
|
...isRecord(record["needsConfirmation"]) ? { needsConfirmation: record["needsConfirmation"] } : {}
|
|
8553
8567
|
};
|
|
8554
8568
|
}
|
|
@@ -13791,6 +13805,72 @@ var init_activate = __esm({
|
|
|
13791
13805
|
}
|
|
13792
13806
|
});
|
|
13793
13807
|
|
|
13808
|
+
// packages/cli/src/run-presence.ts
|
|
13809
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
13810
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync10, readFileSync as readFileSync29, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
|
|
13811
|
+
import path41 from "node:path";
|
|
13812
|
+
function presenceDir() {
|
|
13813
|
+
return path41.join(path41.dirname(sessionPath()), "runs");
|
|
13814
|
+
}
|
|
13815
|
+
function presenceFile(componentName) {
|
|
13816
|
+
return path41.join(presenceDir(), `${createHash8("sha256").update(componentName).digest("hex").slice(0, 16)}.json`);
|
|
13817
|
+
}
|
|
13818
|
+
function readCached(componentName) {
|
|
13819
|
+
const file = presenceFile(componentName);
|
|
13820
|
+
if (!existsSync32(file)) return void 0;
|
|
13821
|
+
try {
|
|
13822
|
+
const parsed = JSON.parse(readFileSync29(file, "utf8"));
|
|
13823
|
+
return typeof parsed.runId === "string" && typeof parsed.origin === "string" ? parsed : void 0;
|
|
13824
|
+
} catch {
|
|
13825
|
+
return void 0;
|
|
13826
|
+
}
|
|
13827
|
+
}
|
|
13828
|
+
async function post(origin, token, pathname, body, method = "POST") {
|
|
13829
|
+
const response = await fetch(`${origin}${pathname}`, {
|
|
13830
|
+
method,
|
|
13831
|
+
headers: { authorization: `Bearer ${token}`, ...body === void 0 ? {} : { "content-type": "application/json" } },
|
|
13832
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
13833
|
+
signal: AbortSignal.timeout(PRESENCE_TIMEOUT_MS)
|
|
13834
|
+
});
|
|
13835
|
+
if (!response.ok) throw new Error(String(response.status));
|
|
13836
|
+
return response.json();
|
|
13837
|
+
}
|
|
13838
|
+
async function reportRunPresence(componentName, phase) {
|
|
13839
|
+
try {
|
|
13840
|
+
const session = readStoredSession();
|
|
13841
|
+
if (session === void 0) return;
|
|
13842
|
+
const cached2 = readCached(componentName);
|
|
13843
|
+
if (cached2 !== void 0 && cached2.origin === session.origin) {
|
|
13844
|
+
const beat = await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, { phase });
|
|
13845
|
+
if (beat.alive === true) return;
|
|
13846
|
+
}
|
|
13847
|
+
const started = await post(session.origin, session.token, "/api/runs", { componentName, phase });
|
|
13848
|
+
if (typeof started.runId !== "string") return;
|
|
13849
|
+
mkdirSync10(presenceDir(), { recursive: true });
|
|
13850
|
+
writeFileSync14(presenceFile(componentName), `${JSON.stringify({ runId: started.runId, origin: session.origin })}
|
|
13851
|
+
`, { mode: 384 });
|
|
13852
|
+
} catch {
|
|
13853
|
+
}
|
|
13854
|
+
}
|
|
13855
|
+
async function endRunPresence(componentName) {
|
|
13856
|
+
try {
|
|
13857
|
+
const session = readStoredSession();
|
|
13858
|
+
const cached2 = readCached(componentName);
|
|
13859
|
+
rmSync5(presenceFile(componentName), { force: true });
|
|
13860
|
+
if (session === void 0 || cached2 === void 0 || cached2.origin !== session.origin) return;
|
|
13861
|
+
await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, void 0, "DELETE");
|
|
13862
|
+
} catch {
|
|
13863
|
+
}
|
|
13864
|
+
}
|
|
13865
|
+
var PRESENCE_TIMEOUT_MS;
|
|
13866
|
+
var init_run_presence = __esm({
|
|
13867
|
+
"packages/cli/src/run-presence.ts"() {
|
|
13868
|
+
"use strict";
|
|
13869
|
+
init_publish_client();
|
|
13870
|
+
PRESENCE_TIMEOUT_MS = 1500;
|
|
13871
|
+
}
|
|
13872
|
+
});
|
|
13873
|
+
|
|
13794
13874
|
// packages/cli/src/commands/compose.ts
|
|
13795
13875
|
var compose_exports = {};
|
|
13796
13876
|
__export(compose_exports, {
|
|
@@ -13799,15 +13879,15 @@ __export(compose_exports, {
|
|
|
13799
13879
|
compositionPairsFor: () => compositionPairsFor,
|
|
13800
13880
|
runCompose: () => runCompose
|
|
13801
13881
|
});
|
|
13802
|
-
import { createHash as
|
|
13803
|
-
import { existsSync as
|
|
13804
|
-
import
|
|
13882
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
13883
|
+
import { existsSync as existsSync33, readFileSync as readFileSync30, readdirSync as readdirSync14 } from "node:fs";
|
|
13884
|
+
import path42 from "node:path";
|
|
13805
13885
|
function compositionPairsFor(hostSet, roots) {
|
|
13806
|
-
const parent =
|
|
13886
|
+
const parent = path42.dirname(hostSet);
|
|
13807
13887
|
const explicitRoots = [...new Set(roots)];
|
|
13808
13888
|
let skippedParent;
|
|
13809
13889
|
let parentRoot = [];
|
|
13810
|
-
if (!explicitRoots.some((r) =>
|
|
13890
|
+
if (!explicitRoots.some((r) => path42.resolve(r) === path42.resolve(parent))) {
|
|
13811
13891
|
let parentEntries = 0;
|
|
13812
13892
|
try {
|
|
13813
13893
|
parentEntries = readdirSync14(parent).length;
|
|
@@ -13848,7 +13928,7 @@ function substitutionPairs(edges, hostSet) {
|
|
|
13848
13928
|
});
|
|
13849
13929
|
}
|
|
13850
13930
|
const pair = pairs.get(key);
|
|
13851
|
-
const poseDisplay = e.pose.reps.map((r) => `${
|
|
13931
|
+
const poseDisplay = e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ");
|
|
13852
13932
|
pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
|
|
13853
13933
|
for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
|
|
13854
13934
|
}
|
|
@@ -13860,7 +13940,7 @@ function runCompose(flags) {
|
|
|
13860
13940
|
return;
|
|
13861
13941
|
}
|
|
13862
13942
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
13863
|
-
const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) =>
|
|
13943
|
+
const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path42.resolve(base, d)) : [base];
|
|
13864
13944
|
if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
|
|
13865
13945
|
fail(flags, ExitCode.InputValidation, {
|
|
13866
13946
|
error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
|
|
@@ -13869,7 +13949,7 @@ function runCompose(flags) {
|
|
|
13869
13949
|
});
|
|
13870
13950
|
}
|
|
13871
13951
|
if (flags.set !== void 0) {
|
|
13872
|
-
runComposeConfirm(flags,
|
|
13952
|
+
runComposeConfirm(flags, path42.resolve(base, flags.set), roots);
|
|
13873
13953
|
return;
|
|
13874
13954
|
}
|
|
13875
13955
|
const index = buildComposeIndex(roots);
|
|
@@ -13887,7 +13967,7 @@ function runCompose(flags) {
|
|
|
13887
13967
|
}
|
|
13888
13968
|
let lastHost = "";
|
|
13889
13969
|
for (const e of edges) {
|
|
13890
|
-
const host = `${
|
|
13970
|
+
const host = `${path42.basename(e.hostSet)}`;
|
|
13891
13971
|
if (host !== lastHost) {
|
|
13892
13972
|
process.stdout.write(`
|
|
13893
13973
|
${host}
|
|
@@ -13895,7 +13975,7 @@ ${host}
|
|
|
13895
13975
|
lastHost = host;
|
|
13896
13976
|
}
|
|
13897
13977
|
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) => `${
|
|
13978
|
+
const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
|
|
13899
13979
|
process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
|
|
13900
13980
|
`);
|
|
13901
13981
|
for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
|
|
@@ -13907,14 +13987,14 @@ ${NOTE}
|
|
|
13907
13987
|
});
|
|
13908
13988
|
}
|
|
13909
13989
|
function runComposeConfirm(flags, hostSet, roots) {
|
|
13910
|
-
if (!
|
|
13990
|
+
if (!existsSync33(path42.join(hostSet, "recording-set.json"))) {
|
|
13911
13991
|
fail(flags, ExitCode.InputValidation, {
|
|
13912
13992
|
error: `no recording-set.json in ${hostSet}`,
|
|
13913
13993
|
code: "no-recording-set",
|
|
13914
13994
|
remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
|
|
13915
13995
|
});
|
|
13916
13996
|
}
|
|
13917
|
-
const scanRoots = [.../* @__PURE__ */ new Set([...roots,
|
|
13997
|
+
const scanRoots = [.../* @__PURE__ */ new Set([...roots, path42.dirname(hostSet)])];
|
|
13918
13998
|
const index = buildComposeIndex(scanRoots);
|
|
13919
13999
|
const edges = composeReport(index);
|
|
13920
14000
|
const pairs = substitutionPairs(edges, hostSet);
|
|
@@ -13997,7 +14077,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
|
|
|
13997
14077
|
// full recording-set hash join lands with pin authoring, where
|
|
13998
14078
|
// task configs exist.)
|
|
13999
14079
|
manifestSha256: Object.fromEntries(
|
|
14000
|
-
p.partnerDirs.map((d) => [
|
|
14080
|
+
p.partnerDirs.map((d) => [path42.relative(hostSet, d), createHash9("sha256").update(readFileSync30(path42.join(d, "recording-set.json"))).digest("hex")])
|
|
14001
14081
|
)
|
|
14002
14082
|
},
|
|
14003
14083
|
// Schema shape ONLY (review: the display-side poseDisplay field
|
|
@@ -14077,10 +14157,10 @@ __export(record_exports, {
|
|
|
14077
14157
|
runRecordPlan: () => runRecordPlan,
|
|
14078
14158
|
runRecordStatus: () => runRecordStatus
|
|
14079
14159
|
});
|
|
14080
|
-
import { existsSync as
|
|
14160
|
+
import { existsSync as existsSync34, mkdtempSync as mkdtempSync2, readFileSync as readFileSync31, readdirSync as readdirSync15 } from "node:fs";
|
|
14081
14161
|
import os7 from "node:os";
|
|
14082
|
-
import
|
|
14083
|
-
import { writeFileSync as
|
|
14162
|
+
import path43 from "node:path";
|
|
14163
|
+
import { writeFileSync as writeFileSync15 } from "node:fs";
|
|
14084
14164
|
function recordsInteractionState(reports) {
|
|
14085
14165
|
const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
|
|
14086
14166
|
return reports.some((r) => evident(r.axis) || r.domain.some(evident));
|
|
@@ -14102,7 +14182,7 @@ function interactionDisclosure(component, reports) {
|
|
|
14102
14182
|
};
|
|
14103
14183
|
}
|
|
14104
14184
|
function symbolsFromMetadataEnvelope(file, sourceFrame) {
|
|
14105
|
-
const env = JSON.parse(
|
|
14185
|
+
const env = JSON.parse(readFileSync31(file, "utf8"));
|
|
14106
14186
|
const text = env.content.map((c) => c.text ?? "").join("\n");
|
|
14107
14187
|
const symbols = [];
|
|
14108
14188
|
const walk2 = (node, ancestor) => {
|
|
@@ -14160,8 +14240,8 @@ function runRecordPlan(opts) {
|
|
|
14160
14240
|
if (rawFile !== void 0) {
|
|
14161
14241
|
try {
|
|
14162
14242
|
const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
|
|
14163
|
-
const tmp =
|
|
14164
|
-
|
|
14243
|
+
const tmp = path43.join(mkdtempSync2(path43.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
|
|
14244
|
+
writeFileSync15(tmp, JSON.stringify(envelope));
|
|
14165
14245
|
metadataEntries.push({ file: tmp });
|
|
14166
14246
|
} catch (err) {
|
|
14167
14247
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -14182,7 +14262,7 @@ function runRecordPlan(opts) {
|
|
|
14182
14262
|
let metadataTruncated = false;
|
|
14183
14263
|
for (const { file, frame } of metadataEntries) {
|
|
14184
14264
|
try {
|
|
14185
|
-
const parsed = symbolsFromMetadataEnvelope(
|
|
14265
|
+
const parsed = symbolsFromMetadataEnvelope(path43.resolve(file), frame);
|
|
14186
14266
|
symbols.push(...parsed.symbols);
|
|
14187
14267
|
if (parsed.truncated) metadataTruncated = true;
|
|
14188
14268
|
} catch (err) {
|
|
@@ -14216,7 +14296,7 @@ function runRecordPlan(opts) {
|
|
|
14216
14296
|
if (symbols.length === 0) {
|
|
14217
14297
|
const leads = metadataEntries.flatMap(({ file }) => {
|
|
14218
14298
|
try {
|
|
14219
|
-
const env = JSON.parse(
|
|
14299
|
+
const env = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
|
|
14220
14300
|
return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
|
|
14221
14301
|
} catch {
|
|
14222
14302
|
return [];
|
|
@@ -14309,7 +14389,7 @@ function runRecordPlan(opts) {
|
|
|
14309
14389
|
// a CLI flag and the MCP surface has no parameter for it.
|
|
14310
14390
|
decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
|
|
14311
14391
|
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(
|
|
14392
|
+
userRuns: [`rm ${quoteArg(path43.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
|
|
14313
14393
|
},
|
|
14314
14394
|
{
|
|
14315
14395
|
id: "larger-allowance",
|
|
@@ -14319,6 +14399,7 @@ function runRecordPlan(opts) {
|
|
|
14319
14399
|
],
|
|
14320
14400
|
limitHintIfWhoamiIsSilent: LIMIT_HINT
|
|
14321
14401
|
};
|
|
14402
|
+
void reportRunPresence(opts.component, "recording");
|
|
14322
14403
|
emitData(
|
|
14323
14404
|
opts,
|
|
14324
14405
|
{
|
|
@@ -14507,7 +14588,7 @@ function runRecordNext(opts) {
|
|
|
14507
14588
|
const progress = payload["progress"];
|
|
14508
14589
|
process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
|
|
14509
14590
|
\u2192 ${payload["note"]}
|
|
14510
|
-
\u2192 then: ${tendrilCommand(`record ingest --set ${
|
|
14591
|
+
\u2192 then: ${tendrilCommand(`record ingest --set ${path43.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
|
|
14511
14592
|
`);
|
|
14512
14593
|
});
|
|
14513
14594
|
}
|
|
@@ -14581,7 +14662,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
14581
14662
|
const skipped = [];
|
|
14582
14663
|
const failed = [];
|
|
14583
14664
|
for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
|
|
14584
|
-
if (
|
|
14665
|
+
if (existsSync34(path43.join(setDir, rep, name))) {
|
|
14585
14666
|
skipped.push(name);
|
|
14586
14667
|
continue;
|
|
14587
14668
|
}
|
|
@@ -14603,16 +14684,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
14603
14684
|
}
|
|
14604
14685
|
function rawEnvelopeFromFile(file, parts) {
|
|
14605
14686
|
if (parts) {
|
|
14606
|
-
const blocks = JSON.parse(
|
|
14687
|
+
const blocks = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
|
|
14607
14688
|
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
14689
|
return { content: blocks.map((text) => ({ type: "text", text })) };
|
|
14609
14690
|
}
|
|
14610
|
-
return { content: [{ type: "text", text:
|
|
14691
|
+
return { content: [{ type: "text", text: readFileSync31(path43.resolve(file), "utf8") }] };
|
|
14611
14692
|
}
|
|
14612
14693
|
async function runRecordIngest(opts) {
|
|
14613
14694
|
let payload;
|
|
14614
14695
|
try {
|
|
14615
|
-
payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(
|
|
14696
|
+
payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync31(path43.resolve(opts.file), "utf8"));
|
|
14616
14697
|
} catch (err) {
|
|
14617
14698
|
fail(opts, ExitCode.InputValidation, {
|
|
14618
14699
|
error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -14624,7 +14705,7 @@ async function runRecordIngest(opts) {
|
|
|
14624
14705
|
fail(opts, ExitCode.InputValidation, {
|
|
14625
14706
|
error: "--raw is for text tool responses; screenshots are binary",
|
|
14626
14707
|
code: "envelope-invalid",
|
|
14627
|
-
remediation: `Use \`${tendrilCommand(`record fetch --set ${
|
|
14708
|
+
remediation: `Use \`${tendrilCommand(`record fetch --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
|
|
14628
14709
|
});
|
|
14629
14710
|
}
|
|
14630
14711
|
if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
|
|
@@ -14644,7 +14725,7 @@ async function runRecordIngest(opts) {
|
|
|
14644
14725
|
remediation: REINGEST_GUIDANCE
|
|
14645
14726
|
});
|
|
14646
14727
|
}
|
|
14647
|
-
|
|
14728
|
+
writeFileSync15(path43.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
|
|
14648
14729
|
`);
|
|
14649
14730
|
emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
|
|
14650
14731
|
process.stdout.write("set-level get_variable_defs ingested\n");
|
|
@@ -14668,7 +14749,7 @@ async function runRecordIngest(opts) {
|
|
|
14668
14749
|
remediation: REINGEST_GUIDANCE
|
|
14669
14750
|
});
|
|
14670
14751
|
}
|
|
14671
|
-
|
|
14752
|
+
writeFileSync15(path43.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
|
|
14672
14753
|
`);
|
|
14673
14754
|
emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
|
|
14674
14755
|
process.stdout.write("set-level get_motion_context ingested\n");
|
|
@@ -14684,7 +14765,7 @@ async function runRecordIngest(opts) {
|
|
|
14684
14765
|
if (assets !== void 0) {
|
|
14685
14766
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
14686
14767
|
`);
|
|
14687
|
-
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${
|
|
14768
|
+
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
14769
|
`);
|
|
14689
14770
|
}
|
|
14690
14771
|
});
|
|
@@ -14757,14 +14838,14 @@ async function runRecordIngestRep(opts) {
|
|
|
14757
14838
|
if (assets !== void 0) {
|
|
14758
14839
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
14759
14840
|
`);
|
|
14760
|
-
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${
|
|
14841
|
+
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
14842
|
`);
|
|
14762
14843
|
}
|
|
14763
14844
|
});
|
|
14764
14845
|
}
|
|
14765
14846
|
function runRecordAsset(opts) {
|
|
14766
14847
|
if (opts.dir !== void 0) {
|
|
14767
|
-
const dir =
|
|
14848
|
+
const dir = path43.resolve(opts.dir);
|
|
14768
14849
|
const names = readdirSync15(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
|
|
14769
14850
|
if (names.length === 0) {
|
|
14770
14851
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -14776,7 +14857,7 @@ function runRecordAsset(opts) {
|
|
|
14776
14857
|
const ingested = [];
|
|
14777
14858
|
try {
|
|
14778
14859
|
for (const name of names) {
|
|
14779
|
-
ingestAsset(opts.setDir, opts.rep, name,
|
|
14860
|
+
ingestAsset(opts.setDir, opts.rep, name, readFileSync31(path43.join(dir, name)));
|
|
14780
14861
|
ingested.push(name);
|
|
14781
14862
|
}
|
|
14782
14863
|
} catch (err) {
|
|
@@ -14796,11 +14877,11 @@ function runRecordAsset(opts) {
|
|
|
14796
14877
|
fail(opts, ExitCode.InputValidation, {
|
|
14797
14878
|
error: "pass --name and --file for a single asset, or --dir for a batch",
|
|
14798
14879
|
code: "asset-rejected",
|
|
14799
|
-
remediation: tendrilCommand(`record asset --set ${
|
|
14880
|
+
remediation: tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
|
|
14800
14881
|
});
|
|
14801
14882
|
}
|
|
14802
14883
|
try {
|
|
14803
|
-
ingestAsset(opts.setDir, opts.rep, opts.name,
|
|
14884
|
+
ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync31(path43.resolve(opts.file)));
|
|
14804
14885
|
emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
|
|
14805
14886
|
process.stdout.write(`ingested ${opts.rep}/${opts.name}
|
|
14806
14887
|
`);
|
|
@@ -14817,8 +14898,8 @@ function runRecordStatus(opts) {
|
|
|
14817
14898
|
const status = sessionStatus(opts.setDir);
|
|
14818
14899
|
const composition = (() => {
|
|
14819
14900
|
try {
|
|
14820
|
-
const setDir =
|
|
14821
|
-
const { open, standing, invalid } = compositionPairsFor(setDir, [
|
|
14901
|
+
const setDir = path43.resolve(opts.setDir);
|
|
14902
|
+
const { open, standing, invalid } = compositionPairsFor(setDir, [path43.dirname(setDir)]);
|
|
14822
14903
|
return {
|
|
14823
14904
|
openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
|
|
14824
14905
|
confirmed: standing.filter((s) => s.status === "confirmed").length,
|
|
@@ -14839,7 +14920,7 @@ function runRecordStatus(opts) {
|
|
|
14839
14920
|
}
|
|
14840
14921
|
}
|
|
14841
14922
|
process.stdout.write(
|
|
14842
|
-
status.motion.recorded ? motionTruthFor(
|
|
14923
|
+
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
14924
|
` : "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
14925
|
);
|
|
14845
14926
|
if ("unavailable" in composition) {
|
|
@@ -14847,7 +14928,7 @@ function runRecordStatus(opts) {
|
|
|
14847
14928
|
`);
|
|
14848
14929
|
} else if (composition.openPairs.length > 0) {
|
|
14849
14930
|
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 ${
|
|
14931
|
+
`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
14932
|
`
|
|
14852
14933
|
);
|
|
14853
14934
|
} else if (composition.confirmed > 0) {
|
|
@@ -14887,7 +14968,7 @@ function narrowedRoles(derived, override) {
|
|
|
14887
14968
|
function rolesFromFile(opts, file, derived) {
|
|
14888
14969
|
let json;
|
|
14889
14970
|
try {
|
|
14890
|
-
json = JSON.parse(
|
|
14971
|
+
json = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
|
|
14891
14972
|
} catch (err) {
|
|
14892
14973
|
fail(opts, ExitCode.InputValidation, {
|
|
14893
14974
|
error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
@@ -14925,11 +15006,11 @@ function rolesFromFile(opts, file, derived) {
|
|
|
14925
15006
|
};
|
|
14926
15007
|
}
|
|
14927
15008
|
function runRecordFinish(opts) {
|
|
14928
|
-
if (!
|
|
15009
|
+
if (!existsSync34(path43.join(opts.setDir, "recording-set.json"))) {
|
|
14929
15010
|
fail(opts, ExitCode.InputValidation, {
|
|
14930
15011
|
error: `no recording-set.json in ${opts.setDir}`,
|
|
14931
15012
|
code: "no-recording-set",
|
|
14932
|
-
remediation: `Run \`${tendrilCommand(`record plan --set ${
|
|
15013
|
+
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
15014
|
});
|
|
14934
15015
|
}
|
|
14935
15016
|
const { manifest, raw } = readManifestFile(opts.setDir);
|
|
@@ -14957,17 +15038,17 @@ function runRecordFinish(opts) {
|
|
|
14957
15038
|
fail(opts, ExitCode.ConfirmationRequired, {
|
|
14958
15039
|
error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
|
|
14959
15040
|
code: "roles-confirmation-not-interactive",
|
|
14960
|
-
remediation: `A human runs \`${tendrilCommand(`record finish --set ${
|
|
15041
|
+
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
15042
|
});
|
|
14962
15043
|
}
|
|
14963
15044
|
const merged = { ...raw, roles };
|
|
14964
|
-
const { issues } = validateRecordingSet(merged, (rel) =>
|
|
15045
|
+
const { issues } = validateRecordingSet(merged, (rel) => existsSync34(path43.join(opts.setDir, rel)));
|
|
14965
15046
|
const errors = issues.filter((i) => i.severity === "error");
|
|
14966
15047
|
if (errors.length > 0) {
|
|
14967
15048
|
fail(opts, ExitCode.InputValidation, {
|
|
14968
15049
|
error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
|
|
14969
15050
|
code: "recording-set-invalid",
|
|
14970
|
-
remediation: `Fix the set (\`${tendrilCommand(`record status --set ${
|
|
15051
|
+
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
15052
|
});
|
|
14972
15053
|
}
|
|
14973
15054
|
for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
|
|
@@ -14986,6 +15067,7 @@ var init_record = __esm({
|
|
|
14986
15067
|
init_src();
|
|
14987
15068
|
init_src4();
|
|
14988
15069
|
init_output();
|
|
15070
|
+
init_run_presence();
|
|
14989
15071
|
init_entitlement();
|
|
14990
15072
|
init_invocation();
|
|
14991
15073
|
init_compose2();
|
|
@@ -15017,9 +15099,9 @@ var init_record = __esm({
|
|
|
15017
15099
|
});
|
|
15018
15100
|
|
|
15019
15101
|
// packages/cli/src/font-guidance.ts
|
|
15020
|
-
import
|
|
15102
|
+
import path44 from "node:path";
|
|
15021
15103
|
function fontsUnprovenRemediation(setDir) {
|
|
15022
|
-
const set = setDir === void 0 ? void 0 :
|
|
15104
|
+
const set = setDir === void 0 ? void 0 : path44.resolve(setDir);
|
|
15023
15105
|
if (set !== void 0) {
|
|
15024
15106
|
try {
|
|
15025
15107
|
const needs = recordedFontNeeds(set);
|
|
@@ -15094,8 +15176,8 @@ __export(fonts_exports, {
|
|
|
15094
15176
|
runFontsResolveSet: () => runFontsResolveSet,
|
|
15095
15177
|
runFontsStatus: () => runFontsStatus
|
|
15096
15178
|
});
|
|
15097
|
-
import { existsSync as
|
|
15098
|
-
import
|
|
15179
|
+
import { existsSync as existsSync35, readFileSync as readFileSync32 } from "node:fs";
|
|
15180
|
+
import path45 from "node:path";
|
|
15099
15181
|
async function runFontsResolve(opts) {
|
|
15100
15182
|
const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
|
|
15101
15183
|
const queryRefusal = systemFaceRefusal(opts.family.trim());
|
|
@@ -15116,7 +15198,7 @@ async function runFontsResolve(opts) {
|
|
|
15116
15198
|
}
|
|
15117
15199
|
}
|
|
15118
15200
|
async function runFontsResolveSet(opts) {
|
|
15119
|
-
const setDir =
|
|
15201
|
+
const setDir = path45.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
|
|
15120
15202
|
let needs = [];
|
|
15121
15203
|
try {
|
|
15122
15204
|
needs = recordedFontNeeds(setDir);
|
|
@@ -15211,16 +15293,16 @@ async function runFontsResolveSet(opts) {
|
|
|
15211
15293
|
}
|
|
15212
15294
|
}
|
|
15213
15295
|
function runFontsStatus(opts) {
|
|
15214
|
-
const manifestPath2 =
|
|
15215
|
-
if (!
|
|
15296
|
+
const manifestPath2 = path45.join(opts.cacheDir, "manifest.json");
|
|
15297
|
+
if (!existsSync35(manifestPath2)) {
|
|
15216
15298
|
fail(opts, ExitCode.FontsUnproven, {
|
|
15217
15299
|
error: `no font cache at ${opts.cacheDir}`,
|
|
15218
15300
|
code: "fonts-unresolved",
|
|
15219
15301
|
remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
|
|
15220
15302
|
});
|
|
15221
15303
|
}
|
|
15222
|
-
const faces = JSON.parse(
|
|
15223
|
-
const lockVerdicts = opts.lock !== void 0 ? checkFontLock(
|
|
15304
|
+
const faces = JSON.parse(readFileSync32(manifestPath2, "utf8"));
|
|
15305
|
+
const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path45.resolve(opts.lock), opts.cacheDir) : null;
|
|
15224
15306
|
emitData(opts, { faces, lockVerdicts }, () => {
|
|
15225
15307
|
for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
|
|
15226
15308
|
`);
|
|
@@ -15264,13 +15346,13 @@ function familyMismatch(family, declared) {
|
|
|
15264
15346
|
}
|
|
15265
15347
|
function runFontsAdd(opts) {
|
|
15266
15348
|
if (opts.set !== void 0) {
|
|
15267
|
-
const declared = taskFontFamilies(
|
|
15349
|
+
const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
|
|
15268
15350
|
const mismatch = familyMismatch(opts.family, declared);
|
|
15269
15351
|
if (mismatch !== void 0) {
|
|
15270
15352
|
fail(opts, ExitCode.InputValidation, {
|
|
15271
15353
|
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
15354
|
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 ${
|
|
15355
|
+
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
15356
|
});
|
|
15275
15357
|
}
|
|
15276
15358
|
} else {
|
|
@@ -15329,13 +15411,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
|
|
|
15329
15411
|
}
|
|
15330
15412
|
function runFontsAddSystem(opts) {
|
|
15331
15413
|
if (opts.set !== void 0) {
|
|
15332
|
-
const declared = taskFontFamilies(
|
|
15414
|
+
const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
|
|
15333
15415
|
const mismatch = familyMismatch(opts.family, declared);
|
|
15334
15416
|
if (mismatch !== void 0) {
|
|
15335
15417
|
fail(opts, ExitCode.InputValidation, {
|
|
15336
15418
|
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
15419
|
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(
|
|
15420
|
+
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
15421
|
});
|
|
15340
15422
|
}
|
|
15341
15423
|
} else {
|
|
@@ -15385,12 +15467,12 @@ var init_fonts = __esm({
|
|
|
15385
15467
|
});
|
|
15386
15468
|
|
|
15387
15469
|
// packages/cli/src/profile-input.ts
|
|
15388
|
-
import { existsSync as
|
|
15389
|
-
import
|
|
15470
|
+
import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
|
|
15471
|
+
import path46 from "node:path";
|
|
15390
15472
|
function loadCodebaseProfile(flags, profilePath) {
|
|
15391
15473
|
if (profilePath === void 0) return null;
|
|
15392
|
-
const abs =
|
|
15393
|
-
if (!
|
|
15474
|
+
const abs = path46.resolve(profilePath);
|
|
15475
|
+
if (!existsSync36(abs)) {
|
|
15394
15476
|
fail(flags, ExitCode.InputValidation, {
|
|
15395
15477
|
error: `no profile at ${abs}`,
|
|
15396
15478
|
code: "profile_missing",
|
|
@@ -15398,7 +15480,7 @@ function loadCodebaseProfile(flags, profilePath) {
|
|
|
15398
15480
|
});
|
|
15399
15481
|
}
|
|
15400
15482
|
try {
|
|
15401
|
-
return readCodebaseProfile(
|
|
15483
|
+
return readCodebaseProfile(readFileSync33(abs, "utf8"));
|
|
15402
15484
|
} catch (error) {
|
|
15403
15485
|
fail(flags, ExitCode.InputValidation, {
|
|
15404
15486
|
error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -15440,8 +15522,8 @@ __export(verify_exports, {
|
|
|
15440
15522
|
runVerify: () => runVerify,
|
|
15441
15523
|
verdictCaveatsFor: () => verdictCaveatsFor
|
|
15442
15524
|
});
|
|
15443
|
-
import { existsSync as
|
|
15444
|
-
import
|
|
15525
|
+
import { existsSync as existsSync37, readFileSync as readFileSync34, rmSync as rmSync6, writeFileSync as writeFileSync16 } from "node:fs";
|
|
15526
|
+
import path47 from "node:path";
|
|
15445
15527
|
function interactionCoverage(behaviors) {
|
|
15446
15528
|
const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
|
|
15447
15529
|
const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
|
|
@@ -15682,12 +15764,12 @@ function compositionReport(input) {
|
|
|
15682
15764
|
function eyeCheck(bundleDir) {
|
|
15683
15765
|
return {
|
|
15684
15766
|
command: tendrilCommand(`inspect "${bundleDir}"`),
|
|
15685
|
-
sheetPath:
|
|
15767
|
+
sheetPath: path47.join(bundleDir, "verify-evidence", "inspect.html"),
|
|
15686
15768
|
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
15769
|
};
|
|
15688
15770
|
}
|
|
15689
15771
|
function evidenceArtifacts(evidenceDir, reps) {
|
|
15690
|
-
const named = (name) =>
|
|
15772
|
+
const named = (name) => existsSync37(path47.join(evidenceDir, name)) ? name : null;
|
|
15691
15773
|
return {
|
|
15692
15774
|
legend: named("diff-legend.txt"),
|
|
15693
15775
|
configs: reps.map((rep) => {
|
|
@@ -15735,7 +15817,7 @@ function taskFromManifest(opts, manifest, setDir) {
|
|
|
15735
15817
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
15736
15818
|
const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
15737
15819
|
const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
|
|
15738
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
15820
|
+
const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
|
|
15739
15821
|
const authored = (() => {
|
|
15740
15822
|
if (registry !== void 0) return void 0;
|
|
15741
15823
|
try {
|
|
@@ -15796,19 +15878,19 @@ function verdictCaveatsFor(input) {
|
|
|
15796
15878
|
async function runVerify(opts) {
|
|
15797
15879
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
15798
15880
|
let recordingSetDrift;
|
|
15799
|
-
const setOverride = opts.set !== void 0 ?
|
|
15800
|
-
opts = { ...opts, bundleDir:
|
|
15801
|
-
if (!
|
|
15881
|
+
const setOverride = opts.set !== void 0 ? path47.resolve(callerCwd, opts.set) : void 0;
|
|
15882
|
+
opts = { ...opts, bundleDir: path47.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
|
|
15883
|
+
if (!existsSync37(opts.bundleDir)) {
|
|
15802
15884
|
fail(opts, ExitCode.InputValidation, {
|
|
15803
15885
|
error: `bundle directory not found: ${opts.bundleDir}`,
|
|
15804
15886
|
code: "bundle-missing",
|
|
15805
15887
|
remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
|
|
15806
15888
|
});
|
|
15807
15889
|
}
|
|
15808
|
-
const manifestPath2 =
|
|
15890
|
+
const manifestPath2 = path47.join(opts.bundleDir, "component.json");
|
|
15809
15891
|
let manifest;
|
|
15810
|
-
if (
|
|
15811
|
-
const { manifest: parsed, issues } = readBundleManifest(
|
|
15892
|
+
if (existsSync37(manifestPath2)) {
|
|
15893
|
+
const { manifest: parsed, issues } = readBundleManifest(readFileSync34(manifestPath2, "utf8"));
|
|
15812
15894
|
if (issues.length > 0) {
|
|
15813
15895
|
fail(opts, ExitCode.InputValidation, {
|
|
15814
15896
|
error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
|
|
@@ -15839,21 +15921,21 @@ async function runVerify(opts) {
|
|
|
15839
15921
|
task = registry;
|
|
15840
15922
|
} else if (manifest !== void 0) {
|
|
15841
15923
|
const resolveSetDir = (p) => {
|
|
15842
|
-
if (
|
|
15843
|
-
const fromRepo =
|
|
15844
|
-
if (
|
|
15845
|
-
return
|
|
15924
|
+
if (path47.isAbsolute(p)) return p;
|
|
15925
|
+
const fromRepo = path47.resolve(REPO_ROOT, p);
|
|
15926
|
+
if (existsSync37(fromRepo)) return fromRepo;
|
|
15927
|
+
return path47.resolve(callerCwd, p);
|
|
15846
15928
|
};
|
|
15847
15929
|
const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
|
|
15848
|
-
if (!
|
|
15930
|
+
if (!existsSync37(path47.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path47.resolve(t.set) === path47.resolve(setDir))) {
|
|
15849
15931
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
15850
15932
|
error: `recording set not found or unmanifested: ${setDir}`,
|
|
15851
15933
|
code: "recording-set-missing",
|
|
15852
15934
|
remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
|
|
15853
15935
|
});
|
|
15854
15936
|
}
|
|
15855
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
15856
|
-
if (registry !== void 0 && !
|
|
15937
|
+
const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
|
|
15938
|
+
if (registry !== void 0 && !existsSync37(path47.join(setDir, "recording-set.json"))) {
|
|
15857
15939
|
const recordedSlugs = registry.configs.map((c) => c.rep);
|
|
15858
15940
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
15859
15941
|
unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
@@ -15885,9 +15967,9 @@ async function runVerify(opts) {
|
|
|
15885
15967
|
recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
|
|
15886
15968
|
}
|
|
15887
15969
|
for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
|
|
15888
|
-
const p =
|
|
15889
|
-
if (!
|
|
15890
|
-
const issues = checkBundleSourceFile(name, new Uint8Array(
|
|
15970
|
+
const p = path47.join(opts.bundleDir, name);
|
|
15971
|
+
if (!existsSync37(p)) continue;
|
|
15972
|
+
const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync34(p)));
|
|
15891
15973
|
if (issues.length > 0) {
|
|
15892
15974
|
fail(opts, ExitCode.InputValidation, {
|
|
15893
15975
|
error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
|
|
@@ -15925,7 +16007,7 @@ async function runVerify(opts) {
|
|
|
15925
16007
|
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
16008
|
}
|
|
15927
16009
|
const missing = task.configs.filter(
|
|
15928
|
-
(c) => !
|
|
16010
|
+
(c) => !existsSync37(path47.join(task.set, c.rep, "get_screenshot.json")) || !existsSync37(path47.join(task.set, c.rep, "get_metadata.json"))
|
|
15929
16011
|
);
|
|
15930
16012
|
if (missing.length > 0) {
|
|
15931
16013
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
@@ -15935,8 +16017,8 @@ async function runVerify(opts) {
|
|
|
15935
16017
|
});
|
|
15936
16018
|
}
|
|
15937
16019
|
const bar = BARS2[opts.bar];
|
|
15938
|
-
const evidenceDir =
|
|
15939
|
-
|
|
16020
|
+
const evidenceDir = path47.join(opts.bundleDir, "verify-evidence");
|
|
16021
|
+
rmSync6(evidenceDir, { recursive: true, force: true });
|
|
15940
16022
|
const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
15941
16023
|
const quality = await checkBundleQuality(
|
|
15942
16024
|
opts.bundleDir,
|
|
@@ -15953,7 +16035,7 @@ async function runVerify(opts) {
|
|
|
15953
16035
|
// ASKED, never "follows every convention".
|
|
15954
16036
|
loadCodebaseProfile(opts, opts.profile) ?? void 0
|
|
15955
16037
|
);
|
|
15956
|
-
const bundleCss = ["tokens.css", "styles.css"].map((f) =>
|
|
16038
|
+
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
16039
|
const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
|
|
15958
16040
|
const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs, {
|
|
15959
16041
|
...opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {},
|
|
@@ -15974,10 +16056,10 @@ async function runVerify(opts) {
|
|
|
15974
16056
|
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
16057
|
}
|
|
15976
16058
|
const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
15977
|
-
const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ?
|
|
16059
|
+
const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path47.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
|
|
15978
16060
|
const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
|
|
15979
16061
|
const renderExpectations = verifyPins.pins.map((pin) => ({
|
|
15980
|
-
modulePath:
|
|
16062
|
+
modulePath: path47.join(composedModuleDir(pin.partnerName), pin.entryComponent),
|
|
15981
16063
|
component: pin.entryComponent,
|
|
15982
16064
|
stampName: `${pin.pairKey}:${pin.entryComponent}`,
|
|
15983
16065
|
hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
|
|
@@ -16270,7 +16352,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
16270
16352
|
}
|
|
16271
16353
|
process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
|
|
16272
16354
|
`);
|
|
16273
|
-
const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) =>
|
|
16355
|
+
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
16356
|
if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
|
|
16275
16357
|
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
16358
|
`);
|
|
@@ -16325,7 +16407,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
16325
16407
|
persistReport(opts, report, evidenceDir);
|
|
16326
16408
|
}
|
|
16327
16409
|
function persistReport(opts, report, evidenceDir) {
|
|
16328
|
-
if (!
|
|
16410
|
+
if (!existsSync37(evidenceDir)) return;
|
|
16329
16411
|
const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
|
|
16330
16412
|
const withExit = {
|
|
16331
16413
|
...report,
|
|
@@ -16333,9 +16415,9 @@ function persistReport(opts, report, evidenceDir) {
|
|
|
16333
16415
|
...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
|
|
16334
16416
|
};
|
|
16335
16417
|
try {
|
|
16336
|
-
|
|
16337
|
-
|
|
16338
|
-
`${JSON.stringify(verifyReportForTransport(withExit,
|
|
16418
|
+
writeFileSync16(
|
|
16419
|
+
path47.join(evidenceDir, VERIFY_REPORT_FILENAME),
|
|
16420
|
+
`${JSON.stringify(verifyReportForTransport(withExit, path47.basename(opts.bundleDir)), null, 2)}
|
|
16339
16421
|
`
|
|
16340
16422
|
);
|
|
16341
16423
|
} catch (e) {
|
|
@@ -16385,11 +16467,11 @@ __export(engine_exports, {
|
|
|
16385
16467
|
runEngineBrief: () => runEngineBrief,
|
|
16386
16468
|
runEngineScore: () => runEngineScore
|
|
16387
16469
|
});
|
|
16388
|
-
import { appendFileSync, existsSync as
|
|
16389
|
-
import
|
|
16470
|
+
import { appendFileSync, existsSync as existsSync38, mkdirSync as mkdirSync11, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "node:fs";
|
|
16471
|
+
import path48 from "node:path";
|
|
16390
16472
|
function resolveEngineTask(opts, callerCwd) {
|
|
16391
|
-
const asPath =
|
|
16392
|
-
const isSet =
|
|
16473
|
+
const asPath = path48.resolve(callerCwd, opts.taskOrSet);
|
|
16474
|
+
const isSet = existsSync38(path48.join(asPath, "recording-set.json"));
|
|
16393
16475
|
const registry = TASKS[opts.taskOrSet];
|
|
16394
16476
|
if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
|
|
16395
16477
|
if (isSet) {
|
|
@@ -16398,7 +16480,7 @@ function resolveEngineTask(opts, callerCwd) {
|
|
|
16398
16480
|
for (const d of authored.disclosures) warn(opts, d);
|
|
16399
16481
|
return {
|
|
16400
16482
|
task: authored.task,
|
|
16401
|
-
name:
|
|
16483
|
+
name: path48.basename(asPath),
|
|
16402
16484
|
ref: asPath,
|
|
16403
16485
|
disclosures: authored.disclosures,
|
|
16404
16486
|
interactionEvidence: authored.api.interactionEvidence,
|
|
@@ -16425,10 +16507,11 @@ function runEngineBrief(opts) {
|
|
|
16425
16507
|
requireEntitlement(opts);
|
|
16426
16508
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16427
16509
|
const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
|
|
16510
|
+
void reportRunPresence(name, "implementing");
|
|
16428
16511
|
const bar = BARS3[opts.bar];
|
|
16429
|
-
if (
|
|
16512
|
+
if (existsSync38(path48.join(task.set, "recording-set.json"))) {
|
|
16430
16513
|
try {
|
|
16431
|
-
const { open, skippedParent } = compositionPairsFor(
|
|
16514
|
+
const { open, skippedParent } = compositionPairsFor(path48.resolve(task.set), [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
|
|
16432
16515
|
if (skippedParent !== void 0) {
|
|
16433
16516
|
disclosures.push(
|
|
16434
16517
|
`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 +16520,7 @@ function runEngineBrief(opts) {
|
|
|
16437
16520
|
if (open.length > 0) {
|
|
16438
16521
|
const componentNames = [...new Set(open.map((p) => p.displayName))];
|
|
16439
16522
|
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 ${
|
|
16523
|
+
`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
16524
|
);
|
|
16442
16525
|
}
|
|
16443
16526
|
} catch (err) {
|
|
@@ -16454,9 +16537,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
16454
16537
|
const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
|
|
16455
16538
|
const segments = buildSegments(task, "files");
|
|
16456
16539
|
let notRecorded;
|
|
16457
|
-
const manifestPath2 =
|
|
16458
|
-
if (
|
|
16459
|
-
notRecorded = JSON.parse(
|
|
16540
|
+
const manifestPath2 = path48.join(task.set, "recording-set.json");
|
|
16541
|
+
if (existsSync38(manifestPath2)) {
|
|
16542
|
+
notRecorded = JSON.parse(readFileSync35(manifestPath2, "utf8")).notRecorded;
|
|
16460
16543
|
}
|
|
16461
16544
|
const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
|
|
16462
16545
|
|
|
@@ -16464,7 +16547,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
16464
16547
|
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
16548
|
${notRecorded}` : "";
|
|
16466
16549
|
let fontProvisioning;
|
|
16467
|
-
if (
|
|
16550
|
+
if (existsSync38(manifestPath2)) {
|
|
16468
16551
|
const missingFams = unprovisionedFamilies(task.set);
|
|
16469
16552
|
const unprovided = unprovisionedFaces(task.set);
|
|
16470
16553
|
const weightOnly = missingFams.length === 0;
|
|
@@ -16486,7 +16569,7 @@ ${notRecorded}` : "";
|
|
|
16486
16569
|
};
|
|
16487
16570
|
}
|
|
16488
16571
|
}
|
|
16489
|
-
const pinsResult = composedPins(task.set, [opts.library !== void 0 ?
|
|
16572
|
+
const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
|
|
16490
16573
|
const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
|
|
16491
16574
|
const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
|
|
16492
16575
|
|
|
@@ -16522,10 +16605,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
|
|
|
16522
16605
|
|
|
16523
16606
|
=== TASK PAYLOAD (recorded truth, verbatim) ===
|
|
16524
16607
|
${segments}`;
|
|
16525
|
-
const payloadFile =
|
|
16526
|
-
const candidateDirSuggestion =
|
|
16527
|
-
|
|
16528
|
-
|
|
16608
|
+
const payloadFile = path48.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
|
|
16609
|
+
const candidateDirSuggestion = path48.resolve(callerCwd, `tendril-out/${name}-candidate`);
|
|
16610
|
+
mkdirSync11(path48.dirname(payloadFile), { recursive: true });
|
|
16611
|
+
writeFileSync17(payloadFile, payload);
|
|
16529
16612
|
emitData(
|
|
16530
16613
|
opts,
|
|
16531
16614
|
{
|
|
@@ -16571,7 +16654,7 @@ ${segments}`;
|
|
|
16571
16654
|
// command must search the same bundle roots the pins came
|
|
16572
16655
|
// from, or the oracle and the brief describe different worlds.
|
|
16573
16656
|
`Run \`${tendrilCommand(
|
|
16574
|
-
`engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(
|
|
16657
|
+
`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
16658
|
)}\` \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
16659
|
"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
16660
|
"Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
|
|
@@ -16586,8 +16669,8 @@ ${segments}`;
|
|
|
16586
16669
|
);
|
|
16587
16670
|
}
|
|
16588
16671
|
function appendScoreHistory(candidateDir, entry) {
|
|
16589
|
-
const file =
|
|
16590
|
-
const starts =
|
|
16672
|
+
const file = path48.join(candidateDir, "score-history.jsonl");
|
|
16673
|
+
const starts = existsSync38(file) ? readFileSync35(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
|
|
16591
16674
|
const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
|
|
16592
16675
|
appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
|
|
16593
16676
|
`);
|
|
@@ -16595,9 +16678,10 @@ function appendScoreHistory(candidateDir, entry) {
|
|
|
16595
16678
|
async function runEngineScore(opts) {
|
|
16596
16679
|
requireEntitlement(opts);
|
|
16597
16680
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16598
|
-
const candidateDir =
|
|
16681
|
+
const candidateDir = path48.resolve(callerCwd, opts.candidateDir);
|
|
16599
16682
|
const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
|
|
16600
|
-
|
|
16683
|
+
void reportRunPresence(name, "implementing");
|
|
16684
|
+
if (!existsSync38(candidateDir)) {
|
|
16601
16685
|
fail(opts, ExitCode.InputValidation, {
|
|
16602
16686
|
error: `candidate directory not found: ${candidateDir}`,
|
|
16603
16687
|
code: "candidate-missing",
|
|
@@ -16622,10 +16706,10 @@ async function runEngineScore(opts) {
|
|
|
16622
16706
|
for (const g of missingWeights(task.set)) {
|
|
16623
16707
|
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
16708
|
}
|
|
16625
|
-
if (opts.rebind !== true &&
|
|
16709
|
+
if (opts.rebind !== true && existsSync38(path48.join(candidateDir, "component.json"))) {
|
|
16626
16710
|
const prior = (() => {
|
|
16627
16711
|
try {
|
|
16628
|
-
const read = readBundleManifest(
|
|
16712
|
+
const read = readBundleManifest(readFileSync35(path48.join(candidateDir, "component.json"), "utf8"));
|
|
16629
16713
|
return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
|
|
16630
16714
|
} catch {
|
|
16631
16715
|
return { unreadable: true };
|
|
@@ -16647,13 +16731,13 @@ async function runEngineScore(opts) {
|
|
|
16647
16731
|
}
|
|
16648
16732
|
}
|
|
16649
16733
|
const bar = BARS3[opts.bar];
|
|
16650
|
-
const evidenceDir =
|
|
16734
|
+
const evidenceDir = path48.join(candidateDir, "verify-evidence");
|
|
16651
16735
|
const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
16652
16736
|
const hoverOpts = opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {};
|
|
16653
16737
|
const parity = await checkHoverParity(task, candidateDir, task.configs, { ...hoverOpts, failureShotDir: evidenceDir });
|
|
16654
16738
|
const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
|
|
16655
16739
|
const occlusionFailures = occlusionRows.filter((o) => !o.pass);
|
|
16656
|
-
const scorePins = composedPins(task.set, [opts.library !== void 0 ?
|
|
16740
|
+
const scorePins = composedPins(task.set, [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
|
|
16657
16741
|
const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
|
|
16658
16742
|
const behaviors = [...await checkBehaviors(task, candidateDir, hoverOpts), ...parity, ...composition];
|
|
16659
16743
|
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 +16942,7 @@ var init_engine2 = __esm({
|
|
|
16858
16942
|
init_entitlement();
|
|
16859
16943
|
init_verify();
|
|
16860
16944
|
init_compose2();
|
|
16945
|
+
init_run_presence();
|
|
16861
16946
|
init_src5();
|
|
16862
16947
|
BARS3 = {
|
|
16863
16948
|
pass: { sim: 0.95, ink: 0.95 },
|
|
@@ -16871,11 +16956,11 @@ var codeconnect_exports = {};
|
|
|
16871
16956
|
__export(codeconnect_exports, {
|
|
16872
16957
|
runCodeConnect: () => runCodeConnect
|
|
16873
16958
|
});
|
|
16874
|
-
import { existsSync as
|
|
16875
|
-
import
|
|
16959
|
+
import { existsSync as existsSync39, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
|
|
16960
|
+
import path49 from "node:path";
|
|
16876
16961
|
function runCodeConnect(opts) {
|
|
16877
16962
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16878
|
-
const bundleDir =
|
|
16963
|
+
const bundleDir = path49.resolve(callerCwd, opts.bundleDir);
|
|
16879
16964
|
let url;
|
|
16880
16965
|
try {
|
|
16881
16966
|
url = new URL(opts.figmaUrl);
|
|
@@ -16891,7 +16976,7 @@ function runCodeConnect(opts) {
|
|
|
16891
16976
|
}
|
|
16892
16977
|
let manifest;
|
|
16893
16978
|
try {
|
|
16894
|
-
const read = readBundleManifest(
|
|
16979
|
+
const read = readBundleManifest(readFileSync36(path49.join(bundleDir, "component.json"), "utf8"));
|
|
16895
16980
|
if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
|
|
16896
16981
|
manifest = read.manifest;
|
|
16897
16982
|
} catch (err) {
|
|
@@ -16901,8 +16986,8 @@ function runCodeConnect(opts) {
|
|
|
16901
16986
|
remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
|
|
16902
16987
|
});
|
|
16903
16988
|
}
|
|
16904
|
-
const setDir =
|
|
16905
|
-
if (!
|
|
16989
|
+
const setDir = path49.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
|
|
16990
|
+
if (!existsSync39(path49.join(setDir, "recording-set.json"))) {
|
|
16906
16991
|
fail(opts, ExitCode.InputValidation, {
|
|
16907
16992
|
error: `recording set not found at ${setDir}`,
|
|
16908
16993
|
code: "codeconnect-no-set",
|
|
@@ -16923,10 +17008,10 @@ function runCodeConnect(opts) {
|
|
|
16923
17008
|
const component = api.component;
|
|
16924
17009
|
const recManifest = loadManifest(setDir);
|
|
16925
17010
|
const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
|
|
16926
|
-
const meta =
|
|
16927
|
-
if (!
|
|
17011
|
+
const meta = path49.join(setDir, r.slug, "get_metadata.json");
|
|
17012
|
+
if (!existsSync39(meta)) return void 0;
|
|
16928
17013
|
try {
|
|
16929
|
-
return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(
|
|
17014
|
+
return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync36(meta, "utf8"))))?.[1];
|
|
16930
17015
|
} catch {
|
|
16931
17016
|
return void 0;
|
|
16932
17017
|
}
|
|
@@ -16991,7 +17076,7 @@ function runCodeConnect(opts) {
|
|
|
16991
17076
|
axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
|
|
16992
17077
|
fragmentVars.push(varName);
|
|
16993
17078
|
}
|
|
16994
|
-
const entryRel =
|
|
17079
|
+
const entryRel = path49.relative(callerCwd, path49.join(bundleDir, manifest.entry));
|
|
16995
17080
|
const trust = manifest.trustStatement.split("\n")[0] ?? "";
|
|
16996
17081
|
const lines = [
|
|
16997
17082
|
`// url=${opts.figmaUrl}`,
|
|
@@ -17015,8 +17100,8 @@ function runCodeConnect(opts) {
|
|
|
17015
17100
|
`}`,
|
|
17016
17101
|
``
|
|
17017
17102
|
].join("\n");
|
|
17018
|
-
const outFile =
|
|
17019
|
-
|
|
17103
|
+
const outFile = path49.resolve(callerCwd, opts.out ?? path49.join(bundleDir, `${component}.figma.ts`));
|
|
17104
|
+
writeFileSync18(outFile, lines);
|
|
17020
17105
|
emitData(
|
|
17021
17106
|
opts,
|
|
17022
17107
|
{
|
|
@@ -17054,18 +17139,18 @@ var init_codeconnect = __esm({
|
|
|
17054
17139
|
});
|
|
17055
17140
|
|
|
17056
17141
|
// packages/mcp/src/server.ts
|
|
17057
|
-
import { createHash as
|
|
17058
|
-
import { existsSync as
|
|
17142
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
17143
|
+
import { existsSync as existsSync40, mkdtempSync as mkdtempSync3, readFileSync as readFileSync37, readdirSync as readdirSync16, writeFileSync as writeFileSync19 } from "node:fs";
|
|
17059
17144
|
import os8 from "node:os";
|
|
17060
|
-
import
|
|
17145
|
+
import path50 from "node:path";
|
|
17061
17146
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
17062
17147
|
import { z as z14 } from "zod";
|
|
17063
17148
|
function sourceHash() {
|
|
17064
|
-
const dir =
|
|
17065
|
-
const h =
|
|
17149
|
+
const dir = path50.dirname(fileURLToPath6(import.meta.url));
|
|
17150
|
+
const h = createHash10("sha256");
|
|
17066
17151
|
for (const f of readdirSync16(dir).filter((n) => n.endsWith(".ts")).sort()) {
|
|
17067
17152
|
h.update(f);
|
|
17068
|
-
h.update(
|
|
17153
|
+
h.update(readFileSync37(path50.join(dir, f)));
|
|
17069
17154
|
}
|
|
17070
17155
|
return h.digest("hex").slice(0, 16);
|
|
17071
17156
|
}
|
|
@@ -17073,10 +17158,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
|
|
|
17073
17158
|
var init_server = __esm({
|
|
17074
17159
|
"packages/mcp/src/server.ts"() {
|
|
17075
17160
|
"use strict";
|
|
17076
|
-
REPO_ROOT3 =
|
|
17077
|
-
CLI_BIN =
|
|
17078
|
-
BUNDLED_CLI =
|
|
17079
|
-
CLI_SPAWN =
|
|
17161
|
+
REPO_ROOT3 = path50.resolve(path50.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
|
|
17162
|
+
CLI_BIN = path50.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
|
|
17163
|
+
BUNDLED_CLI = path50.join(path50.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
|
|
17164
|
+
CLI_SPAWN = existsSync40(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
|
|
17080
17165
|
str = (d) => z14.string().describe(d);
|
|
17081
17166
|
optStr = (d) => z14.string().optional().describe(d);
|
|
17082
17167
|
TOOLS = [
|
|
@@ -17107,13 +17192,13 @@ var init_server = __esm({
|
|
|
17107
17192
|
const single = i["metadata"];
|
|
17108
17193
|
const parts = i["metadataParts"];
|
|
17109
17194
|
if (single !== void 0 || parts !== void 0) {
|
|
17110
|
-
const tmp =
|
|
17195
|
+
const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17111
17196
|
if (single !== void 0) {
|
|
17112
|
-
|
|
17197
|
+
writeFileSync19(tmp, single);
|
|
17113
17198
|
argvOut.push("--metadata-raw-file", tmp);
|
|
17114
17199
|
} else {
|
|
17115
17200
|
if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
|
|
17116
|
-
|
|
17201
|
+
writeFileSync19(tmp, JSON.stringify(parts));
|
|
17117
17202
|
argvOut.push("--metadata-raw-parts-file", tmp);
|
|
17118
17203
|
}
|
|
17119
17204
|
}
|
|
@@ -17163,6 +17248,24 @@ var init_server = __esm({
|
|
|
17163
17248
|
schema: z14.object({}),
|
|
17164
17249
|
argv: () => ["login", "--device-wait"]
|
|
17165
17250
|
},
|
|
17251
|
+
{
|
|
17252
|
+
name: "tendril_publish",
|
|
17253
|
+
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, along with which account the result says to be signed in as (they click Approve in the browser; approving includes accepting the design system's publishing terms, which is their decision to make, never yours to urge). 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).",
|
|
17254
|
+
schema: z14.object({
|
|
17255
|
+
bundleDir: str("bundle directory (verified \u2014 carries component.json and verify-evidence)"),
|
|
17256
|
+
portal: optStr("portal origin override for self-hosted portals (defaults to the stored session's portal)")
|
|
17257
|
+
}),
|
|
17258
|
+
argv: (i) => ["publish", i["bundleDir"], "--approve-start", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
|
|
17259
|
+
},
|
|
17260
|
+
{
|
|
17261
|
+
name: "tendril_publish_wait",
|
|
17262
|
+
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.",
|
|
17263
|
+
schema: z14.object({
|
|
17264
|
+
bundleDir: str("the same bundle directory tendril_publish was called with"),
|
|
17265
|
+
portal: optStr("portal origin override (must match tendril_publish's)")
|
|
17266
|
+
}),
|
|
17267
|
+
argv: (i) => ["publish", i["bundleDir"], "--approve-wait", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
|
|
17268
|
+
},
|
|
17166
17269
|
{
|
|
17167
17270
|
name: "tendril_record_next",
|
|
17168
17271
|
annotations: { readOnlyHint: true },
|
|
@@ -17204,14 +17307,14 @@ var init_server = __esm({
|
|
|
17204
17307
|
const bridge = (label, single, parts) => {
|
|
17205
17308
|
if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
|
|
17206
17309
|
if (single === void 0 && parts === void 0) return;
|
|
17207
|
-
const tmp =
|
|
17310
|
+
const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17208
17311
|
if (single !== void 0) {
|
|
17209
|
-
|
|
17312
|
+
writeFileSync19(tmp, single);
|
|
17210
17313
|
argvOut.push(`--${label}-file`, tmp);
|
|
17211
17314
|
} else {
|
|
17212
17315
|
const blocks = parts;
|
|
17213
17316
|
if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
|
|
17214
|
-
|
|
17317
|
+
writeFileSync19(tmp, JSON.stringify(blocks));
|
|
17215
17318
|
argvOut.push(`--${label}-parts-file`, tmp);
|
|
17216
17319
|
}
|
|
17217
17320
|
};
|
|
@@ -17252,12 +17355,12 @@ var init_server = __esm({
|
|
|
17252
17355
|
const file = i["file"];
|
|
17253
17356
|
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
17357
|
if (file !== void 0) return [...base, "--file", file];
|
|
17255
|
-
const tmp =
|
|
17358
|
+
const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17256
17359
|
if (text !== void 0) {
|
|
17257
|
-
|
|
17360
|
+
writeFileSync19(tmp, text);
|
|
17258
17361
|
return [...base, "--file", tmp, "--raw"];
|
|
17259
17362
|
}
|
|
17260
|
-
|
|
17363
|
+
writeFileSync19(tmp, JSON.stringify(texts));
|
|
17261
17364
|
return [...base, "--file", tmp, "--raw-parts"];
|
|
17262
17365
|
}
|
|
17263
17366
|
},
|
|
@@ -17414,13 +17517,13 @@ __export(permissions_exports, {
|
|
|
17414
17517
|
runPermissions: () => runPermissions,
|
|
17415
17518
|
writeSelection: () => writeSelection
|
|
17416
17519
|
});
|
|
17417
|
-
import { existsSync as
|
|
17520
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync12, readFileSync as readFileSync38, writeFileSync as writeFileSync20 } from "node:fs";
|
|
17418
17521
|
import os9 from "node:os";
|
|
17419
|
-
import
|
|
17522
|
+
import path51 from "node:path";
|
|
17420
17523
|
function mergeAllowlist(file, entries, denyEntries = []) {
|
|
17421
17524
|
let settings = {};
|
|
17422
|
-
if (
|
|
17423
|
-
settings = JSON.parse(
|
|
17525
|
+
if (existsSync41(file) && readFileSync38(file, "utf8").trim() !== "") {
|
|
17526
|
+
settings = JSON.parse(readFileSync38(file, "utf8"));
|
|
17424
17527
|
if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
|
|
17425
17528
|
}
|
|
17426
17529
|
const permissions = settings["permissions"] ??= {};
|
|
@@ -17440,8 +17543,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
|
|
|
17440
17543
|
}
|
|
17441
17544
|
if (added.length > 0 || denyAdded.length > 0) {
|
|
17442
17545
|
allow.push(...added);
|
|
17443
|
-
|
|
17444
|
-
|
|
17546
|
+
mkdirSync12(path51.dirname(file), { recursive: true });
|
|
17547
|
+
writeFileSync20(file, `${JSON.stringify(settings, null, 2)}
|
|
17445
17548
|
`);
|
|
17446
17549
|
}
|
|
17447
17550
|
return { added, alreadyPresent, denyAdded };
|
|
@@ -17505,7 +17608,7 @@ async function runPermissions(flags) {
|
|
|
17505
17608
|
}
|
|
17506
17609
|
if (flags.write) {
|
|
17507
17610
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
17508
|
-
const file = flags.user ?
|
|
17611
|
+
const file = flags.user ? path51.join(os9.homedir(), ".claude", "settings.json") : path51.join(base, ".claude", "settings.local.json");
|
|
17509
17612
|
const { entries, denyEntries } = writeSelection(result, flags.user === true);
|
|
17510
17613
|
if (flags.dryRun) {
|
|
17511
17614
|
emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
|
|
@@ -17654,13 +17757,13 @@ __export(inspect_exports, {
|
|
|
17654
17757
|
INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
|
|
17655
17758
|
runInspect: () => runInspect
|
|
17656
17759
|
});
|
|
17657
|
-
import { existsSync as
|
|
17658
|
-
import
|
|
17760
|
+
import { existsSync as existsSync42, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "node:fs";
|
|
17761
|
+
import path52 from "node:path";
|
|
17659
17762
|
function readVerifyReport(evidenceDir) {
|
|
17660
|
-
const p =
|
|
17661
|
-
if (!
|
|
17763
|
+
const p = path52.join(evidenceDir, VERIFY_REPORT_FILENAME);
|
|
17764
|
+
if (!existsSync42(p)) return void 0;
|
|
17662
17765
|
try {
|
|
17663
|
-
return JSON.parse(
|
|
17766
|
+
return JSON.parse(readFileSync39(p, "utf8"));
|
|
17664
17767
|
} catch {
|
|
17665
17768
|
return void 0;
|
|
17666
17769
|
}
|
|
@@ -17688,17 +17791,17 @@ async function runInspect(opts) {
|
|
|
17688
17791
|
printDescription(INSPECT_DESCRIPTION);
|
|
17689
17792
|
return;
|
|
17690
17793
|
}
|
|
17691
|
-
const bundleDir =
|
|
17692
|
-
const evidenceDir =
|
|
17693
|
-
const manifestPath2 =
|
|
17694
|
-
if (!
|
|
17794
|
+
const bundleDir = path52.resolve(opts.bundleDir);
|
|
17795
|
+
const evidenceDir = path52.join(bundleDir, "verify-evidence");
|
|
17796
|
+
const manifestPath2 = path52.join(bundleDir, "component.json");
|
|
17797
|
+
if (!existsSync42(evidenceDir) || !existsSync42(manifestPath2)) {
|
|
17695
17798
|
fail(opts, ExitCode.InputValidation, {
|
|
17696
|
-
error: `nothing to inspect in ${bundleDir} \u2014 ${
|
|
17799
|
+
error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync42(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
|
|
17697
17800
|
code: "no-evidence",
|
|
17698
17801
|
remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
|
|
17699
17802
|
});
|
|
17700
17803
|
}
|
|
17701
|
-
const { manifest } = readBundleManifest(
|
|
17804
|
+
const { manifest } = readBundleManifest(readFileSync39(manifestPath2, "utf8"));
|
|
17702
17805
|
if (manifest === void 0) {
|
|
17703
17806
|
fail(opts, ExitCode.InputValidation, {
|
|
17704
17807
|
error: "component.json did not parse as a bundle manifest",
|
|
@@ -17706,9 +17809,9 @@ async function runInspect(opts) {
|
|
|
17706
17809
|
remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
|
|
17707
17810
|
});
|
|
17708
17811
|
}
|
|
17709
|
-
const setDir =
|
|
17812
|
+
const setDir = path52.resolve(opts.set ?? manifest.provenance.recordingSet.path);
|
|
17710
17813
|
const report = readVerifyReport(evidenceDir);
|
|
17711
|
-
const reps = Object.keys(manifest.propAdapter).filter((rep) =>
|
|
17814
|
+
const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync42(path52.join(evidenceDir, `${rep}-ref.png`)) && existsSync42(path52.join(evidenceDir, `${rep}-render.png`)));
|
|
17712
17815
|
if (reps.length === 0) {
|
|
17713
17816
|
fail(opts, ExitCode.InputValidation, {
|
|
17714
17817
|
error: "verify-evidence holds no ref/render pairs for this bundle's configs",
|
|
@@ -17719,15 +17822,15 @@ async function runInspect(opts) {
|
|
|
17719
17822
|
let crops = 0;
|
|
17720
17823
|
const sections = [];
|
|
17721
17824
|
for (const rep of reps) {
|
|
17722
|
-
const ref = new Uint8Array(
|
|
17723
|
-
const render = new Uint8Array(
|
|
17825
|
+
const ref = new Uint8Array(readFileSync39(path52.join(evidenceDir, `${rep}-ref.png`)));
|
|
17826
|
+
const render = new Uint8Array(readFileSync39(path52.join(evidenceDir, `${rep}-render.png`)));
|
|
17724
17827
|
const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
|
|
17725
17828
|
const cells = [];
|
|
17726
17829
|
for (const [i, n] of nodes.entries()) {
|
|
17727
17830
|
const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
|
|
17728
17831
|
try {
|
|
17729
|
-
|
|
17730
|
-
|
|
17832
|
+
writeFileSync21(path52.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
|
|
17833
|
+
writeFileSync21(path52.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
|
|
17731
17834
|
} catch {
|
|
17732
17835
|
continue;
|
|
17733
17836
|
}
|
|
@@ -17744,8 +17847,8 @@ async function runInspect(opts) {
|
|
|
17744
17847
|
if (reps.includes(c.rep)) continue;
|
|
17745
17848
|
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
17849
|
}
|
|
17747
|
-
const sheet =
|
|
17748
|
-
|
|
17850
|
+
const sheet = path52.join(evidenceDir, "inspect.html");
|
|
17851
|
+
writeFileSync21(
|
|
17749
17852
|
sheet,
|
|
17750
17853
|
`<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
|
|
17751
17854
|
body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
|
|
@@ -17818,8 +17921,8 @@ __export(login_exports, {
|
|
|
17818
17921
|
runLogout: () => runLogout
|
|
17819
17922
|
});
|
|
17820
17923
|
import { spawn } from "node:child_process";
|
|
17821
|
-
import { existsSync as
|
|
17822
|
-
import
|
|
17924
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync13, readFileSync as readFileSync40, rmSync as rmSync7, writeFileSync as writeFileSync22 } from "node:fs";
|
|
17925
|
+
import path53 from "node:path";
|
|
17823
17926
|
import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
|
|
17824
17927
|
async function runLogin(opts, deps) {
|
|
17825
17928
|
const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
|
|
@@ -17913,13 +18016,13 @@ function settleDecision(opts, origin, outcome) {
|
|
|
17913
18016
|
}
|
|
17914
18017
|
}
|
|
17915
18018
|
function pendingLoginPath() {
|
|
17916
|
-
return
|
|
18019
|
+
return path53.join(path53.dirname(sessionPath()), "pending-login.json");
|
|
17917
18020
|
}
|
|
17918
18021
|
async function deviceStartPhase(opts, origin, deps) {
|
|
17919
18022
|
const started = await startHandshake(opts, origin, deps);
|
|
17920
18023
|
const file = pendingLoginPath();
|
|
17921
|
-
|
|
17922
|
-
|
|
18024
|
+
mkdirSync13(path53.dirname(file), { recursive: true });
|
|
18025
|
+
writeFileSync22(file, `${JSON.stringify({ origin, ...started }, null, 2)}
|
|
17923
18026
|
`, { mode: 384 });
|
|
17924
18027
|
deps.openBrowser(started.verificationUrl);
|
|
17925
18028
|
emitData(
|
|
@@ -17944,9 +18047,9 @@ async function deviceStartPhase(opts, origin, deps) {
|
|
|
17944
18047
|
async function deviceWaitPhase(opts, deps) {
|
|
17945
18048
|
const file = pendingLoginPath();
|
|
17946
18049
|
let pending;
|
|
17947
|
-
if (
|
|
18050
|
+
if (existsSync43(file)) {
|
|
17948
18051
|
try {
|
|
17949
|
-
const parsed = JSON.parse(
|
|
18052
|
+
const parsed = JSON.parse(readFileSync40(file, "utf8"));
|
|
17950
18053
|
if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
|
|
17951
18054
|
pending = parsed;
|
|
17952
18055
|
}
|
|
@@ -17960,7 +18063,7 @@ async function deviceWaitPhase(opts, deps) {
|
|
|
17960
18063
|
remediation: `Start one first: ${tendrilCommand("login --device-start")} (the tendril_login tool).`
|
|
17961
18064
|
});
|
|
17962
18065
|
}
|
|
17963
|
-
const done = () =>
|
|
18066
|
+
const done = () => rmSync7(file, { force: true });
|
|
17964
18067
|
const total = Math.ceil(660 / Math.max(1, pending.intervalSeconds));
|
|
17965
18068
|
let ticks = 0;
|
|
17966
18069
|
const outcome = await waitForDecision(opts, pending.origin, deps, pending, () => {
|
|
@@ -18195,12 +18298,13 @@ var init_share = __esm({
|
|
|
18195
18298
|
// packages/cli/src/commands/publish.ts
|
|
18196
18299
|
var publish_exports = {};
|
|
18197
18300
|
__export(publish_exports, {
|
|
18301
|
+
resolveOrigin: () => resolveOrigin,
|
|
18198
18302
|
runPublish: () => runPublish
|
|
18199
18303
|
});
|
|
18200
|
-
import { existsSync as
|
|
18201
|
-
import
|
|
18304
|
+
import { existsSync as existsSync44, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync23 } from "node:fs";
|
|
18305
|
+
import path54 from "node:path";
|
|
18202
18306
|
async function runPublish(opts) {
|
|
18203
|
-
const bundleDir =
|
|
18307
|
+
const bundleDir = path54.resolve(opts.bundleDir);
|
|
18204
18308
|
const bundle = readBundle(opts, bundleDir);
|
|
18205
18309
|
const report = bundle.report;
|
|
18206
18310
|
if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
|
|
@@ -18245,7 +18349,7 @@ async function runPublish(opts) {
|
|
|
18245
18349
|
const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
|
|
18246
18350
|
if (sheetEntry !== void 0) {
|
|
18247
18351
|
const missingCrops = missingInspectCrops(
|
|
18248
|
-
|
|
18352
|
+
readFileSync41(path54.join(bundleDir, sheetEntry.path), "utf8"),
|
|
18249
18353
|
surface.published.map((p) => p.path)
|
|
18250
18354
|
);
|
|
18251
18355
|
if (missingCrops.length > 0) {
|
|
@@ -18310,48 +18414,42 @@ async function runPublish(opts) {
|
|
|
18310
18414
|
);
|
|
18311
18415
|
return;
|
|
18312
18416
|
}
|
|
18313
|
-
const origin = (opts
|
|
18417
|
+
const origin = resolveOrigin(opts);
|
|
18314
18418
|
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
|
-
});
|
|
18419
|
+
if (opts.approveWait === true) {
|
|
18420
|
+
await approveWaitPhase(opts, client, bundleDir, { componentName, figmaFile });
|
|
18321
18421
|
}
|
|
18322
|
-
|
|
18323
|
-
|
|
18324
|
-
if (!accepted.ok) refuse2(opts, accepted, "accept-terms-refused");
|
|
18325
|
-
}
|
|
18326
|
-
const opened = await client.begin({
|
|
18422
|
+
void reportRunPresence(componentName, "publishing");
|
|
18423
|
+
let opened = await client.begin({
|
|
18327
18424
|
componentName,
|
|
18328
18425
|
figmaFile,
|
|
18329
18426
|
entry: bundle.manifest.entry,
|
|
18330
18427
|
files: bundle.files,
|
|
18331
|
-
report: bundle.reportText
|
|
18332
|
-
confirmed: opts.confirmPublish === true
|
|
18428
|
+
report: bundle.reportText
|
|
18333
18429
|
});
|
|
18430
|
+
if (!opened.ok && opened.needsConfirmation !== void 0 && opts.approveWait !== true) {
|
|
18431
|
+
const flow = await runApprovalFlow(opts, client, bundleDir, {
|
|
18432
|
+
componentName,
|
|
18433
|
+
figmaFile,
|
|
18434
|
+
begin: () => client.begin({ componentName, figmaFile, entry: bundle.manifest.entry, files: bundle.files, report: bundle.reportText })
|
|
18435
|
+
});
|
|
18436
|
+
if (flow === void 0) return;
|
|
18437
|
+
opened = flow;
|
|
18438
|
+
}
|
|
18334
18439
|
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
18440
|
if (opened.needsConfirmation !== void 0) {
|
|
18343
18441
|
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18344
|
-
error:
|
|
18442
|
+
error: opened.refusal,
|
|
18345
18443
|
code: "first-publish-unconfirmed",
|
|
18346
|
-
remediation:
|
|
18444
|
+
remediation: "Approve it in your browser \u2014 run the publish again and open the link it prints."
|
|
18347
18445
|
});
|
|
18348
18446
|
}
|
|
18349
18447
|
refuse2(opts, opened, "publish-refused");
|
|
18350
18448
|
}
|
|
18351
18449
|
const uploaded = [];
|
|
18352
18450
|
for (const object of opened.value.plan.objects) {
|
|
18353
|
-
const file =
|
|
18354
|
-
if (!
|
|
18451
|
+
const file = path54.join(bundleDir, object.relPath);
|
|
18452
|
+
if (!existsSync44(file)) {
|
|
18355
18453
|
fail(opts, ExitCode.InputValidation, {
|
|
18356
18454
|
error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
|
|
18357
18455
|
code: "planned-file-missing",
|
|
@@ -18361,12 +18459,15 @@ async function runPublish(opts) {
|
|
|
18361
18459
|
const sent = await client.upload({
|
|
18362
18460
|
publicationId: opened.value.publicationId,
|
|
18363
18461
|
relPath: object.relPath,
|
|
18364
|
-
bytes: new Uint8Array(
|
|
18462
|
+
bytes: new Uint8Array(readFileSync41(file))
|
|
18365
18463
|
});
|
|
18366
|
-
if (!sent.ok) refuse2(opts, sent, "upload-refused");
|
|
18464
|
+
if (!sent.ok) refuse2(opts, sent, "upload-refused", true);
|
|
18367
18465
|
uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
|
|
18368
18466
|
}
|
|
18369
18467
|
const committed = await client.commit({ publicationId: opened.value.publicationId });
|
|
18468
|
+
if (committed.ok) {
|
|
18469
|
+
await endRunPresence(componentName);
|
|
18470
|
+
}
|
|
18370
18471
|
if (!committed.ok) {
|
|
18371
18472
|
if (committed.missing !== void 0 && committed.missing.length > 0) {
|
|
18372
18473
|
fail(opts, ExitCode.General, {
|
|
@@ -18375,7 +18476,7 @@ async function runPublish(opts) {
|
|
|
18375
18476
|
remediation: `Run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication and re-sends only what is missing.`
|
|
18376
18477
|
});
|
|
18377
18478
|
}
|
|
18378
|
-
refuse2(opts, committed, "commit-refused");
|
|
18479
|
+
refuse2(opts, committed, "commit-refused", true);
|
|
18379
18480
|
}
|
|
18380
18481
|
emitData(
|
|
18381
18482
|
opts,
|
|
@@ -18403,23 +18504,23 @@ async function runPublish(opts) {
|
|
|
18403
18504
|
);
|
|
18404
18505
|
}
|
|
18405
18506
|
function readBundle(opts, bundleDir) {
|
|
18406
|
-
const manifestPath2 =
|
|
18407
|
-
const reportPath =
|
|
18408
|
-
if (!
|
|
18507
|
+
const manifestPath2 = path54.join(bundleDir, "component.json");
|
|
18508
|
+
const reportPath = path54.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
|
|
18509
|
+
if (!existsSync44(manifestPath2)) {
|
|
18409
18510
|
fail(opts, ExitCode.InputValidation, {
|
|
18410
18511
|
error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
|
|
18411
18512
|
code: "not-a-bundle",
|
|
18412
18513
|
remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
|
|
18413
18514
|
});
|
|
18414
18515
|
}
|
|
18415
|
-
if (!
|
|
18516
|
+
if (!existsSync44(reportPath)) {
|
|
18416
18517
|
fail(opts, ExitCode.InputValidation, {
|
|
18417
18518
|
error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
|
|
18418
18519
|
code: "bundle-not-verified",
|
|
18419
18520
|
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
18521
|
});
|
|
18421
18522
|
}
|
|
18422
|
-
const { manifest } = readBundleManifest(
|
|
18523
|
+
const { manifest } = readBundleManifest(readFileSync41(manifestPath2, "utf8"));
|
|
18423
18524
|
if (manifest === void 0) {
|
|
18424
18525
|
fail(opts, ExitCode.InputValidation, {
|
|
18425
18526
|
error: "component.json did not parse as a bundle manifest",
|
|
@@ -18427,7 +18528,7 @@ function readBundle(opts, bundleDir) {
|
|
|
18427
18528
|
remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
|
|
18428
18529
|
});
|
|
18429
18530
|
}
|
|
18430
|
-
const reportText =
|
|
18531
|
+
const reportText = readFileSync41(reportPath, "utf8");
|
|
18431
18532
|
let report;
|
|
18432
18533
|
try {
|
|
18433
18534
|
report = JSON.parse(reportText);
|
|
@@ -18447,6 +18548,12 @@ function readBundle(opts, bundleDir) {
|
|
|
18447
18548
|
}
|
|
18448
18549
|
return { manifest, report, reportText, files: bundleFiles(bundleDir) };
|
|
18449
18550
|
}
|
|
18551
|
+
function resolveOrigin(opts) {
|
|
18552
|
+
const named = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
|
|
18553
|
+
if (named !== "") return named;
|
|
18554
|
+
if ((process.env["TENDRIL_TOKEN"] ?? "") !== "") return "";
|
|
18555
|
+
return (readStoredSession()?.origin ?? "").replace(/\/+$/, "");
|
|
18556
|
+
}
|
|
18450
18557
|
function httpClient(opts, origin) {
|
|
18451
18558
|
if (origin === "") {
|
|
18452
18559
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -18479,14 +18586,118 @@ function httpClient(opts, origin) {
|
|
|
18479
18586
|
}
|
|
18480
18587
|
return new HttpPublishClient({ origin, token: found.token });
|
|
18481
18588
|
}
|
|
18482
|
-
function refuse2(opts, sent, code) {
|
|
18589
|
+
function refuse2(opts, sent, code, rejoins = false) {
|
|
18483
18590
|
const detail = sent.detail === void 0 || sent.detail.length === 0 ? "" : `: ${sent.detail.join(", ")}`;
|
|
18591
|
+
const retry = rejoins ? `run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication rather than starting a second one` : `run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again`;
|
|
18484
18592
|
fail(opts, sent.status === 401 ? ExitCode.Auth : ExitCode.General, {
|
|
18485
18593
|
error: `${sent.refusal}${detail}`,
|
|
18486
18594
|
code,
|
|
18487
|
-
remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${(opts
|
|
18595
|
+
remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${resolveOrigin(opts)}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then ${retry}.` : `Fix what is named above and ${retry}.`
|
|
18596
|
+
});
|
|
18597
|
+
}
|
|
18598
|
+
function pendingApprovalPath() {
|
|
18599
|
+
return path54.join(path54.dirname(sessionPath()), "pending-publish.json");
|
|
18600
|
+
}
|
|
18601
|
+
async function runApprovalFlow(opts, client, bundleDir, input) {
|
|
18602
|
+
const requested = await client.requestApproval({ componentName: input.componentName, figmaFile: input.figmaFile });
|
|
18603
|
+
if (!requested.ok) refuse2(opts, requested, "approval-request-refused");
|
|
18604
|
+
const approval = requested.value;
|
|
18605
|
+
const who = await client.whoami?.();
|
|
18606
|
+
const asAccount = who?.ok === true && who.value.email !== "" ? ` signed in as ${who.value.email}` : "";
|
|
18607
|
+
if (opts.approveStart === true) {
|
|
18608
|
+
const pending = { ...approval, bundleDir, componentName: input.componentName, figmaFile: input.figmaFile };
|
|
18609
|
+
writeFileSync23(pendingApprovalPath(), `${JSON.stringify(pending, null, 2)}
|
|
18610
|
+
`, { mode: 384 });
|
|
18611
|
+
await endRunPresence(input.componentName);
|
|
18612
|
+
emitData(
|
|
18613
|
+
opts,
|
|
18614
|
+
{
|
|
18615
|
+
status: "approval-pending",
|
|
18616
|
+
approveUrl: approval.approveUrl,
|
|
18617
|
+
componentName: input.componentName,
|
|
18618
|
+
...asAccount === "" ? {} : { approveAsAccount: who?.ok === true ? who.value.email : "" },
|
|
18619
|
+
expiresAt: approval.expiresAt,
|
|
18620
|
+
next: `open the approve page in the browser${asAccount}, click Approve, then finish with --approve-wait`
|
|
18621
|
+
},
|
|
18622
|
+
() => {
|
|
18623
|
+
process.stdout.write(`Approval requested for ${JSON.stringify(input.componentName)}.
|
|
18624
|
+
`);
|
|
18625
|
+
process.stdout.write(`Approve it here${asAccount}: ${approval.approveUrl}
|
|
18626
|
+
`);
|
|
18627
|
+
process.stdout.write(`Then finish with: ${tendrilCommand(`publish ${opts.bundleDir} --approve-wait`)}
|
|
18628
|
+
`);
|
|
18629
|
+
}
|
|
18630
|
+
);
|
|
18631
|
+
return void 0;
|
|
18632
|
+
}
|
|
18633
|
+
process.stderr.write(`This component's FIRST publish needs your approval in the browser${asAccount}:
|
|
18634
|
+
${approval.approveUrl}
|
|
18635
|
+
`);
|
|
18636
|
+
process.stderr.write(`Waiting for your decision (lapses at ${approval.expiresAt.slice(11, 16)} UTC)\u2026
|
|
18637
|
+
`);
|
|
18638
|
+
const decided = await waitForApproval(opts, client, approval);
|
|
18639
|
+
if (decided === "approved") return input.begin();
|
|
18640
|
+
await endRunPresence(input.componentName);
|
|
18641
|
+
failDecision(opts, decided);
|
|
18642
|
+
}
|
|
18643
|
+
async function approveWaitPhase(opts, client, bundleDir, subject) {
|
|
18644
|
+
const file = pendingApprovalPath();
|
|
18645
|
+
let pending;
|
|
18646
|
+
if (existsSync44(file)) {
|
|
18647
|
+
try {
|
|
18648
|
+
const parsed = JSON.parse(readFileSync41(file, "utf8"));
|
|
18649
|
+
if (typeof parsed.approvalId === "string" && typeof parsed.approveUrl === "string" && typeof parsed.expiresAt === "string") {
|
|
18650
|
+
pending = { pollSeconds: 3, bundleDir, ...parsed };
|
|
18651
|
+
}
|
|
18652
|
+
} catch {
|
|
18653
|
+
}
|
|
18654
|
+
}
|
|
18655
|
+
if (pending === void 0) {
|
|
18656
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18657
|
+
error: "there is no publish approval waiting to finish",
|
|
18658
|
+
code: "no-pending-approval",
|
|
18659
|
+
remediation: `Start one first: ${tendrilCommand(`publish ${opts.bundleDir} --approve-start`)} (the tendril_publish tool).`
|
|
18660
|
+
});
|
|
18661
|
+
}
|
|
18662
|
+
if (pending.componentName !== void 0 && pending.componentName !== subject.componentName) {
|
|
18663
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18664
|
+
error: `the waiting approval is for ${JSON.stringify(pending.componentName)}, and this publish is ${JSON.stringify(subject.componentName)}`,
|
|
18665
|
+
code: "pending-approval-mismatch",
|
|
18666
|
+
remediation: `Finish that one first (${tendrilCommand(`publish ${pending.bundleDir} --approve-wait`)}), or start this one fresh with --approve-start \u2014 starting one replaces the waiting slot.`
|
|
18667
|
+
});
|
|
18668
|
+
}
|
|
18669
|
+
const done = () => rmSync8(file, { force: true });
|
|
18670
|
+
const decided = await waitForApproval(opts, client, pending);
|
|
18671
|
+
done();
|
|
18672
|
+
if (decided !== "approved") failDecision(opts, decided);
|
|
18673
|
+
}
|
|
18674
|
+
async function waitForApproval(opts, client, approval) {
|
|
18675
|
+
const interval = Math.max(1, approval.pollSeconds) * 1e3;
|
|
18676
|
+
const total = Math.ceil(APPROVAL_WAIT_CAP_MS / interval);
|
|
18677
|
+
for (let tick = 1; tick <= total; tick += 1) {
|
|
18678
|
+
const polled = await client.pollApproval({ approvalId: approval.approvalId });
|
|
18679
|
+
if (!polled.ok) refuse2(opts, polled, "approval-poll-refused");
|
|
18680
|
+
if (polled.value.status !== "pending") return polled.value.status;
|
|
18681
|
+
emitProgress(tick, total, "waiting for the browser approval");
|
|
18682
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
18683
|
+
}
|
|
18684
|
+
return "expired";
|
|
18685
|
+
}
|
|
18686
|
+
function failDecision(opts, decided) {
|
|
18687
|
+
if (decided === "denied") {
|
|
18688
|
+
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18689
|
+
error: "the publish was DENIED in the browser \u2014 the human said no",
|
|
18690
|
+
code: "publish-approval-denied",
|
|
18691
|
+
remediation: "Nothing was published. If minds change, run the publish again \u2014 it makes a fresh request."
|
|
18692
|
+
});
|
|
18693
|
+
}
|
|
18694
|
+
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18695
|
+
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",
|
|
18696
|
+
code: "publish-approval-lapsed",
|
|
18697
|
+
remediation: "Run the publish again for a fresh request, and decide it within its 30-minute window. If the approve page showed nothing waiting, the browser is signed in to a DIFFERENT account than this CLI \u2014 the page lists only its own account's requests."
|
|
18488
18698
|
});
|
|
18489
18699
|
}
|
|
18700
|
+
var APPROVAL_WAIT_CAP_MS;
|
|
18490
18701
|
var init_publish = __esm({
|
|
18491
18702
|
"packages/cli/src/commands/publish.ts"() {
|
|
18492
18703
|
"use strict";
|
|
@@ -18495,6 +18706,8 @@ var init_publish = __esm({
|
|
|
18495
18706
|
init_invocation();
|
|
18496
18707
|
init_output();
|
|
18497
18708
|
init_publish_client();
|
|
18709
|
+
init_run_presence();
|
|
18710
|
+
APPROVAL_WAIT_CAP_MS = 31 * 6e4;
|
|
18498
18711
|
}
|
|
18499
18712
|
});
|
|
18500
18713
|
|
|
@@ -18522,17 +18735,17 @@ __export(generate_recorded_exports, {
|
|
|
18522
18735
|
runGenerateRecorded: () => runGenerateRecorded
|
|
18523
18736
|
});
|
|
18524
18737
|
import { confirm as confirm3, isCancel as isCancel4 } from "@clack/prompts";
|
|
18525
|
-
import { existsSync as
|
|
18526
|
-
import
|
|
18738
|
+
import { existsSync as existsSync45, readFileSync as readFileSync42 } from "node:fs";
|
|
18739
|
+
import path55 from "node:path";
|
|
18527
18740
|
async function runGenerateRecorded(opts) {
|
|
18528
18741
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
18529
|
-
const outDirAbs =
|
|
18530
|
-
const recordedAsPath =
|
|
18742
|
+
const outDirAbs = path55.resolve(callerCwd, opts.out);
|
|
18743
|
+
const recordedAsPath = path55.resolve(callerCwd, opts.recorded);
|
|
18531
18744
|
let task;
|
|
18532
18745
|
let taskName;
|
|
18533
18746
|
let authoredApi;
|
|
18534
18747
|
let composition;
|
|
18535
|
-
const isSet =
|
|
18748
|
+
const isSet = existsSync45(path55.join(recordedAsPath, "recording-set.json"));
|
|
18536
18749
|
const registry = TASKS[opts.recorded];
|
|
18537
18750
|
if (registry !== void 0 && !isSet) {
|
|
18538
18751
|
task = registry;
|
|
@@ -18541,7 +18754,7 @@ async function runGenerateRecorded(opts) {
|
|
|
18541
18754
|
try {
|
|
18542
18755
|
const authored = authorTaskFromSet(recordedAsPath);
|
|
18543
18756
|
task = authored.task;
|
|
18544
|
-
taskName =
|
|
18757
|
+
taskName = path55.basename(recordedAsPath);
|
|
18545
18758
|
authoredApi = authored.api;
|
|
18546
18759
|
const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
|
|
18547
18760
|
if (roles.success) composition = roles.data;
|
|
@@ -18575,7 +18788,7 @@ async function runGenerateRecorded(opts) {
|
|
|
18575
18788
|
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
18789
|
}
|
|
18577
18790
|
const missing = task.configs.filter(
|
|
18578
|
-
(c) => !
|
|
18791
|
+
(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
18792
|
);
|
|
18580
18793
|
if (missing.length > 0) {
|
|
18581
18794
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
@@ -18645,8 +18858,8 @@ async function runGenerateRecorded(opts) {
|
|
|
18645
18858
|
` : `${line}
|
|
18646
18859
|
`);
|
|
18647
18860
|
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 ${
|
|
18861
|
+
emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path55.join(outDirAbs, taskName) }, () => {
|
|
18862
|
+
process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path55.join(outDirAbs, taskName)})
|
|
18650
18863
|
`);
|
|
18651
18864
|
});
|
|
18652
18865
|
return;
|
|
@@ -18669,10 +18882,10 @@ async function runGenerateRecorded(opts) {
|
|
|
18669
18882
|
});
|
|
18670
18883
|
}
|
|
18671
18884
|
}
|
|
18672
|
-
const bundleDir =
|
|
18673
|
-
if (
|
|
18885
|
+
const bundleDir = path55.join(outDirAbs, taskName);
|
|
18886
|
+
if (existsSync45(path55.join(bundleDir, "component.json"))) {
|
|
18674
18887
|
try {
|
|
18675
|
-
const prior = readBundleManifest(
|
|
18888
|
+
const prior = readBundleManifest(readFileSync42(path55.join(bundleDir, "component.json"), "utf8")).manifest;
|
|
18676
18889
|
if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
|
|
18677
18890
|
fail(opts, ExitCode.InputValidation, {
|
|
18678
18891
|
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 +20178,7 @@ function buildProgram() {
|
|
|
19965
20178
|
...local["revoke"] !== void 0 ? { revoke: local["revoke"] } : {}
|
|
19966
20179
|
});
|
|
19967
20180
|
});
|
|
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("--
|
|
20181
|
+
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
20182
|
const flags = globalFlags(cmd.parent);
|
|
19970
20183
|
const local = cmd.opts();
|
|
19971
20184
|
const { runPublish: runPublish2 } = await Promise.resolve().then(() => (init_publish(), publish_exports));
|
|
@@ -19974,8 +20187,8 @@ function buildProgram() {
|
|
|
19974
20187
|
bundleDir,
|
|
19975
20188
|
...local["to"] !== void 0 ? { to: local["to"] } : {},
|
|
19976
20189
|
...local["name"] !== void 0 ? { name: local["name"] } : {},
|
|
19977
|
-
...local["
|
|
19978
|
-
...local["
|
|
20190
|
+
...local["approveStart"] !== void 0 ? { approveStart: local["approveStart"] } : {},
|
|
20191
|
+
...local["approveWait"] !== void 0 ? { approveWait: local["approveWait"] } : {}
|
|
19979
20192
|
});
|
|
19980
20193
|
});
|
|
19981
20194
|
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) => {
|