@tendrilapp/cli 0.1.36 → 0.1.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tendril.js +88 -17
- package/package.json +1 -1
package/dist/tendril.js
CHANGED
|
@@ -1067,10 +1067,10 @@ function planSet(setDir, component, symbols, opts = {}) {
|
|
|
1067
1067
|
const scopeUpgraded = opts.variantScope === "component-set" && existing.variantScope !== "component-set";
|
|
1068
1068
|
if (opts.variantScope === "component-set") {
|
|
1069
1069
|
raw["variantScope"] = "component-set";
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1070
|
+
}
|
|
1071
|
+
const variants = symbols.map((s) => s.name).filter((n) => n.includes("="));
|
|
1072
|
+
if (variants.length > 0 && (scopeUpgraded || variants.length > (existing.latticeNames ?? []).length)) {
|
|
1073
|
+
raw["latticeNames"] = variants;
|
|
1074
1074
|
}
|
|
1075
1075
|
if (!wantsNewDefaults || anythingRecorded) {
|
|
1076
1076
|
if (opts.sample !== true) {
|
|
@@ -2759,7 +2759,7 @@ var init_tsc_check = __esm({
|
|
|
2759
2759
|
// packages/verify/src/token-lint.ts
|
|
2760
2760
|
import strictValue from "stylelint-declaration-strict-value";
|
|
2761
2761
|
import stylelint from "stylelint";
|
|
2762
|
-
async function runTokenLint(css, fileLabel = "generated.css", definedVars2, recordedMapEmpty = false) {
|
|
2762
|
+
async function runTokenLint(css, fileLabel = "generated.css", definedVars2, recordedMapEmpty = false, recordedTokensByValue2) {
|
|
2763
2763
|
const result = await stylelint.lint({
|
|
2764
2764
|
code: css,
|
|
2765
2765
|
codeFilename: fileLabel,
|
|
@@ -2777,7 +2777,11 @@ async function runTokenLint(css, fileLabel = "generated.css", definedVars2, reco
|
|
|
2777
2777
|
// noise in the highest-attention output. The finding stays (a
|
|
2778
2778
|
// matching token SHOULD be referenced when one exists); the
|
|
2779
2779
|
// message now carries the escape hatch.
|
|
2780
|
-
message: w.rule === "scale-unlimited/declaration-strict-value" ?
|
|
2780
|
+
message: w.rule === "scale-unlimited/declaration-strict-value" ? (() => {
|
|
2781
|
+
const literal = /"([^"]+)"/.exec(w.text)?.[1] ?? /\b(#[0-9a-fA-F]{3,8}|-?\d*\.?\d+(?:px|rem|em|%))\b/.exec(w.text)?.[1];
|
|
2782
|
+
const token = literal === void 0 ? void 0 : recordedTokensByValue2?.get(literal.trim().toLowerCase());
|
|
2783
|
+
return token !== void 0 ? `${w.text} \u2014 the recorded token map HAS a token for this value: "${token}". Reference it (and emit it in tokens.css) rather than the literal; this is not the known-partial case` : `${w.text} \u2014 ADVISORY: reference the design token if the kit provides one; if it does not, the literal is CORRECT \u2014 never invent a token name (the recorded token map captures one pose's variables and is known-partial)`;
|
|
2784
|
+
})() : w.text
|
|
2781
2785
|
}))
|
|
2782
2786
|
);
|
|
2783
2787
|
const ownDefs = /* @__PURE__ */ new Set();
|
|
@@ -6183,6 +6187,28 @@ function recordedTokenMapState(setDir, reps) {
|
|
|
6183
6187
|
}
|
|
6184
6188
|
return recorded ? "empty" : "never-recorded";
|
|
6185
6189
|
}
|
|
6190
|
+
function recordedTokensByValue(setDir, reps) {
|
|
6191
|
+
const byValue = /* @__PURE__ */ new Map();
|
|
6192
|
+
const readMap = (file) => {
|
|
6193
|
+
if (!existsSync14(file)) return void 0;
|
|
6194
|
+
try {
|
|
6195
|
+
const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync11(file, "utf8"))) || "{}");
|
|
6196
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
6197
|
+
} catch {
|
|
6198
|
+
return void 0;
|
|
6199
|
+
}
|
|
6200
|
+
};
|
|
6201
|
+
const absorb = (m) => {
|
|
6202
|
+
for (const [name, value] of Object.entries(m ?? {})) {
|
|
6203
|
+
if (typeof value !== "string") continue;
|
|
6204
|
+
const key = value.trim().toLowerCase();
|
|
6205
|
+
if (key !== "" && !byValue.has(key)) byValue.set(key, name);
|
|
6206
|
+
}
|
|
6207
|
+
};
|
|
6208
|
+
absorb(readMap(path19.join(setDir, "get_variable_defs.json")));
|
|
6209
|
+
for (const rep of reps) absorb(readMap(path19.join(setDir, rep, "get_variable_defs.json")));
|
|
6210
|
+
return byValue;
|
|
6211
|
+
}
|
|
6186
6212
|
function scannable(css) {
|
|
6187
6213
|
const blank = (m) => m.replace(/[^\n]/g, " ");
|
|
6188
6214
|
return css.replace(/\/\*[^]*?\*\//g, blank).replace(/@font-face\s*{[^}]*}/g, blank);
|
|
@@ -6284,7 +6310,8 @@ async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, c
|
|
|
6284
6310
|
}
|
|
6285
6311
|
if (css !== "") {
|
|
6286
6312
|
const mapState = set === void 0 ? void 0 : recordedTokenMapState(set.dir, set.reps);
|
|
6287
|
-
|
|
6313
|
+
const tokensByValue = set === void 0 ? void 0 : recordedTokensByValue(set.dir, set.reps);
|
|
6314
|
+
for (const v of (await runTokenLint(css, "styles.css", definedVars(tokensCss), mapState !== void 0 && mapState !== "populated", tokensByValue)).violations) {
|
|
6288
6315
|
findings.push({ kind: "token-lint", file: "styles.css", line: v.line, message: v.message });
|
|
6289
6316
|
}
|
|
6290
6317
|
if (mapState === "never-recorded") {
|
|
@@ -11885,7 +11912,7 @@ function motionBriefSection(setDir) {
|
|
|
11885
11912
|
const truthBlock = truth.state === "recorded" ? `RECORDED MOTION TRUTH (verbatim from the set's get_motion_context recording \u2014 where it states concrete durations/curves for a transition, those WIN over the default tiers below for THAT transition; where it is ambiguous, fall back to the tiers and say so in your report \u2014 never invent motion the recording does not state):
|
|
11886
11913
|
${truth.text.length > RECORDED_QUOTE_CAP ? `${truth.text.slice(0, RECORDED_QUOTE_CAP)}
|
|
11887
11914
|
[truncated at ${RECORDED_QUOTE_CAP} chars \u2014 the full recording is ${setDir}/get_motion_context.json]` : truth.text}
|
|
11888
|
-
` : truth.state === "recorded-no-motion" ? "RECORDED MOTION TRUTH:
|
|
11915
|
+
` : truth.state === "recorded-no-motion" ? "RECORDED MOTION TRUTH: NONE CAPTURED. The set's get_motion_context recording came back with every motion field null \u2014 no keyframe tracks, no code snippets, no timeline duration. That is the INSTRUMENT reporting no keyframe/timeline animation on this node; it is NOT evidence that the design defines no transitions (the tool advertises keyframe/timeline data, and whether it reports prototype/Smart-Animate reactions is UNPROVEN \u2014 ADR-016 residual). The doctrine below is DEFAULT DOCTRINE, not recorded truth; report that recorded motion truth was UNAVAILABLE \u2014 never that the design has none.\n" : truth.state === "recorded-empty" ? "RECORDED MOTION TRUTH: NONE CAPTURED. The set's get_motion_context recording is EMPTY \u2014 the instrument returned no text at all. Treat motion as UNRECORDED, not as proof the design has none. Apply the default doctrine below.\n" : truth.state === "asked-not-recorded" ? "RECORDED MOTION TRUTH: none yet \u2014 the plan asked for get_motion_context but it is not recorded (or the recorded file is unusable; `record status` names which). The doctrine below is DEFAULT DOCTRINE, not recorded truth.\n" : "RECORDED MOTION TRUTH: never asked \u2014 this set predates the motion-capture obligation. The doctrine below is DEFAULT DOCTRINE, not recorded truth.\n";
|
|
11889
11916
|
return `
|
|
11890
11917
|
|
|
11891
11918
|
=== MOTION (PRESCRIBED, NOT VERIFIED \u2014 ADR-016) ===
|
|
@@ -11948,6 +11975,7 @@ var init_src7 = __esm({
|
|
|
11948
11975
|
// packages/cli/src/commands/record.ts
|
|
11949
11976
|
var record_exports = {};
|
|
11950
11977
|
__export(record_exports, {
|
|
11978
|
+
SELECTION_SCOPE_DISCLOSURE: () => SELECTION_SCOPE_DISCLOSURE,
|
|
11951
11979
|
instanceLeads: () => instanceLeads,
|
|
11952
11980
|
interactionDisclosure: () => interactionDisclosure,
|
|
11953
11981
|
isFigmaAssetUrl: () => isFigmaAssetUrl,
|
|
@@ -12165,10 +12193,14 @@ function runRecordPlan(opts) {
|
|
|
12165
12193
|
const poses = manifest.reps.length;
|
|
12166
12194
|
const callLow = poses * FIGMA_CALLS_PER_REP;
|
|
12167
12195
|
const callHigh = poses * FIGMA_CALLS_PER_REP_WORST;
|
|
12168
|
-
const
|
|
12169
|
-
const
|
|
12170
|
-
const
|
|
12171
|
-
const
|
|
12196
|
+
const planStatus = sessionStatus(opts.setDir);
|
|
12197
|
+
const posesRemaining = planStatus.reps.filter((r) => r.missing.length > 0).length;
|
|
12198
|
+
const setLevelRemaining = (planStatus.setTokenMapRecorded ? 0 : 1) + (planStatus.motion.asked && !planStatus.motion.recorded ? 1 : 0);
|
|
12199
|
+
const callsNeeded = posesRemaining * FIGMA_CALLS_PER_REP + setLevelRemaining;
|
|
12200
|
+
const setLevelClause = setLevelRemaining > 0 ? ` + ${setLevelRemaining} set-level call(s)` : "";
|
|
12201
|
+
const callSentence = posesRemaining === poses ? `${poses} poses \xD7 ${FIGMA_CALLS_PER_REP} calls per pose${setLevelClause} = ${callsNeeded} Figma calls` : `${posesRemaining} poses still to record \xD7 ${FIGMA_CALLS_PER_REP} calls per pose${setLevelClause} = ${callsNeeded} Figma calls (${poses - posesRemaining} of ${poses} already on disk)`;
|
|
12202
|
+
const planningSpent = variantScope === "selection" ? poses : 1;
|
|
12203
|
+
const arithmetic = `${callSentence} (up to ${posesRemaining * FIGMA_CALLS_PER_REP_WORST + setLevelRemaining} if screenshots or assets need a second fetch). ALREADY SPENT on planning, and counting against the same allowance: ~${planningSpent} get_metadata call(s)${variantScope === "selection" ? " \u2014 a selection-scoped plan pays one per node handed in, not one for the set" : ""}.`;
|
|
12172
12204
|
const multiDay = callsNeeded > MULTI_DAY_CALL_THRESHOLD;
|
|
12173
12205
|
const feasibilityCheck = {
|
|
12174
12206
|
arithmetic,
|
|
@@ -12323,7 +12355,24 @@ function nextPayload(setDir) {
|
|
|
12323
12355
|
const manifest = loadManifest(setDir);
|
|
12324
12356
|
const frameNode = Object.keys(manifest.sourceFrames ?? {})[0] ?? manifest.reps[0]?.nodeId ?? "";
|
|
12325
12357
|
const invalidNote = status.setTokenMapInvalid !== void 0 ? ` (the existing set-level file is UNUSABLE \u2014 ${status.setTokenMapInvalid} \u2014 re-record it)` : "";
|
|
12326
|
-
return {
|
|
12358
|
+
return {
|
|
12359
|
+
slug: "__set__",
|
|
12360
|
+
nodeId: frameNode,
|
|
12361
|
+
tool: "get_variable_defs",
|
|
12362
|
+
note: `SET-LEVEL (required \u2014 NOT a resume pointer; do this even under parallel recording): call get_variable_defs on the component frame and ingest with --rep __set__.${invalidNote} ${ENVELOPE_HELP}`,
|
|
12363
|
+
// SET-LEVEL REQUIRED, NOT A RESUME POINTER (run 25, the
|
|
12364
|
+
// SECOND field occurrence — this is a payload defect, not a
|
|
12365
|
+
// prompting accident). `next` overloads two different things:
|
|
12366
|
+
// a per-rep resume pointer, which parallel recorders are told
|
|
12367
|
+
// to IGNORE, and the set-level steps, which nobody may ignore
|
|
12368
|
+
// or the set never completes. Twice now a recorder followed
|
|
12369
|
+
// that instruction literally, refused this step, and reported
|
|
12370
|
+
// it as prompt injection. The scope field lets an ignore-next
|
|
12371
|
+
// instruction target only the pointer.
|
|
12372
|
+
scope: "set-level-required",
|
|
12373
|
+
ignorable: false,
|
|
12374
|
+
progress
|
|
12375
|
+
};
|
|
12327
12376
|
}
|
|
12328
12377
|
if (instruction === null && status.motion.asked && !status.motion.recorded) {
|
|
12329
12378
|
const manifest = loadManifest(setDir);
|
|
@@ -12334,7 +12383,18 @@ function nextPayload(setDir) {
|
|
|
12334
12383
|
slug: "__set__",
|
|
12335
12384
|
nodeId: motionNode,
|
|
12336
12385
|
tool: "get_motion_context",
|
|
12337
|
-
note: `SET-LEVEL: call get_motion_context on ${nodeKind} and ingest with --rep __set__ \u2014 it returns Figma's KEYFRAME/TIMELINE data. A response whose motion fields are all null is a VALID recording (record it too): it means no keyframe animation was REPORTED for this node, which is not the same as the design defining no transitions (whether this tool reports prototype/Smart-Animate reactions is unproven \u2014 run 24).${invalidNote} ${ENVELOPE_HELP}`,
|
|
12386
|
+
note: `SET-LEVEL (required \u2014 NOT a resume pointer; do this even under parallel recording): call get_motion_context on ${nodeKind} and ingest with --rep __set__ \u2014 it returns Figma's KEYFRAME/TIMELINE data. A response whose motion fields are all null is a VALID recording (record it too): it means no keyframe animation was REPORTED for this node, which is not the same as the design defining no transitions (whether this tool reports prototype/Smart-Animate reactions is unproven \u2014 run 24).${invalidNote} ${ENVELOPE_HELP}`,
|
|
12387
|
+
// SET-LEVEL REQUIRED, NOT A RESUME POINTER (run 25, the
|
|
12388
|
+
// SECOND field occurrence — this is a payload defect, not a
|
|
12389
|
+
// prompting accident). `next` overloads two different things:
|
|
12390
|
+
// a per-rep resume pointer, which parallel recorders are told
|
|
12391
|
+
// to IGNORE, and the set-level steps, which nobody may ignore
|
|
12392
|
+
// or the set never completes. Twice now a recorder followed
|
|
12393
|
+
// that instruction literally, refused this step, and reported
|
|
12394
|
+
// it as prompt injection. The scope field lets an ignore-next
|
|
12395
|
+
// instruction target only the pointer.
|
|
12396
|
+
scope: "set-level-required",
|
|
12397
|
+
ignorable: false,
|
|
12338
12398
|
progress
|
|
12339
12399
|
};
|
|
12340
12400
|
}
|
|
@@ -12848,7 +12908,7 @@ var init_record = __esm({
|
|
|
12848
12908
|
FIGMA_CALLS_PER_REP = 3;
|
|
12849
12909
|
FIGMA_CALLS_PER_REP_WORST = 4;
|
|
12850
12910
|
MULTI_DAY_CALL_THRESHOLD = 600;
|
|
12851
|
-
SELECTION_SCOPE_DISCLOSURE = "variantsFound counts the nodes you passed, NOT the component set: this metadata carried no enclosing set, so nothing here can tell whether the set holds more variants. Do NOT report the lattice as complete on this basis.
|
|
12911
|
+
SELECTION_SCOPE_DISCLOSURE = "variantsFound counts the nodes you passed, NOT the component set: this metadata carried no enclosing set, so nothing here can tell whether the set holds more variants. Do NOT report the lattice as complete on this basis. WORTH ONE ATTEMPT, NOT A HUNT: call get_metadata WITHOUT a nodeId (a page listing), and if a plausible enclosing component set is there, call get_metadata on that page and re-plan against the set \u2014 re-planning is free until the first envelope is ingested. STOP AFTER THAT. The set may not be reachable at all: it can sit outside any page you can list, and the component-listing fallback is gated behind an Org/Enterprise seat. That is a NORMAL outcome, not a failure \u2014 coverage stays UNKNOWN, every scored config is still verified, and the report says so honestly. Do not spend further calls chasing it; say plainly that coverage is unestablished and move on.";
|
|
12852
12912
|
LIMIT_HINT = {
|
|
12853
12913
|
status: "UNVERIFIED \u2014 recorded 2026-08-14 from Figma's published documentation and one field report; Figma changes these at will.",
|
|
12854
12914
|
use: "Use ONLY if whoami states no allowance, and say it is unverified when you show it. The allowance whoami reports always wins.",
|
|
@@ -13434,7 +13494,16 @@ function latticeCoverage(setManifest, scoredConfigs) {
|
|
|
13434
13494
|
latticeConfigs: null,
|
|
13435
13495
|
// The count is still worth reporting — it is just not a
|
|
13436
13496
|
// denominator, and the name has to stop implying that it is.
|
|
13437
|
-
|
|
13497
|
+
// A count SMALLER than what was scored is provably STALE (run 25:
|
|
13498
|
+
// a selection-scoped set topped up 1 → 16 reps kept its first
|
|
13499
|
+
// plan's count, and the integer sat two lines from
|
|
13500
|
+
// scoredConfigs: 16). Report the fact honestly instead of a
|
|
13501
|
+
// number that contradicts its own neighbour; the root fix
|
|
13502
|
+
// refreshes the manifest on re-plan, but manifests written
|
|
13503
|
+
// before it exist.
|
|
13504
|
+
...lattice >= scoredConfigs ? { variantsPlanned: lattice } : {
|
|
13505
|
+
variantsPlannedUnreliable: `the manifest records ${lattice} planned variant name(s) but ${scoredConfigs} config(s) were scored \u2014 this count predates a top-up and is NOT a denominator; re-plan the set to refresh it`
|
|
13506
|
+
},
|
|
13438
13507
|
latticeUnestablished,
|
|
13439
13508
|
...notRecorded
|
|
13440
13509
|
};
|
|
@@ -13531,6 +13600,7 @@ function verdictCaveatsFor(input) {
|
|
|
13531
13600
|
...input.latticeBlock["latticeConfigs"] === null ? ["coverage-denominator-unknown"] : [],
|
|
13532
13601
|
...unrecorded || notRecorded ? ["poses-unrecorded"] : [],
|
|
13533
13602
|
...input.operability !== "verified" ? ["operability-unverified"] : [],
|
|
13603
|
+
...(input.pixelOnlyInteractionPoses ?? 0) > 0 ? ["interaction-poses-unchecked"] : [],
|
|
13534
13604
|
...input.compositionUnavailable ? ["composition-not-checked"] : [],
|
|
13535
13605
|
...input.fontsSubstituted ? ["fonts-substituted"] : []
|
|
13536
13606
|
];
|
|
@@ -13836,7 +13906,8 @@ async function runVerify(opts) {
|
|
|
13836
13906
|
latticeBlock,
|
|
13837
13907
|
operability: coverage.operability,
|
|
13838
13908
|
compositionUnavailable: "unavailable" in compositionBlock,
|
|
13839
|
-
fontsSubstituted: substitutedFamilies.length > 0
|
|
13909
|
+
fontsSubstituted: substitutedFamilies.length > 0,
|
|
13910
|
+
pixelOnlyInteractionPoses: unmappedInteractionEvidence.length
|
|
13840
13911
|
}),
|
|
13841
13912
|
// Machine-readable cause (adversarial review): a cert-bar failure
|
|
13842
13913
|
// with zero pixel/behavior/composition failures was only
|