@tendrilapp/cli 0.1.29 → 0.1.31
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/SKILL.md +13 -3
- package/dist/tendril-mcp.js +7 -2
- package/dist/tendril.js +60 -12
- package/package.json +1 -1
package/dist/SKILL.md
CHANGED
|
@@ -131,7 +131,12 @@ Batch runs (several components in one session):
|
|
|
131
131
|
2. `tendril_record_plan` with the response text passed VERBATIM via
|
|
132
132
|
`metadataParts` (one array entry per response block, in order —
|
|
133
133
|
single-block responses may use `metadata` instead; nothing is saved
|
|
134
|
-
to a file, and never hand-join blocks)
|
|
134
|
+
to a file, and never hand-join blocks), AND `figmaFile` set to the
|
|
135
|
+
file key from the URL you were handed (figma.com/design/<KEY>/… —
|
|
136
|
+
pass exactly KEY): it is recorded as the set's file identity for
|
|
137
|
+
cross-bundle composition, captured at plan time only and never
|
|
138
|
+
backfillable — a set planned without it can never join across
|
|
139
|
+
bundles → the queue plan. The plan
|
|
135
140
|
records the FULL variant matrix — every pose the set defines
|
|
136
141
|
(sampling exists only as a human-run CLI flag; the MCP surface
|
|
137
142
|
cannot sample). CROSS-CHECK `variantsFound` in the plan output
|
|
@@ -254,8 +259,13 @@ Batch runs (several components in one session):
|
|
|
254
259
|
CSS. Import every React API you use.
|
|
255
260
|
3. `tendril_engine_score` → read the per-config results and feedback.
|
|
256
261
|
Fix FAIL configs without regressing PASS configs; re-score.
|
|
257
|
-
4. Stop when `allPass` is true
|
|
258
|
-
|
|
262
|
+
4. Stop when `allPass` is true. Two consecutive rounds with no
|
|
263
|
+
improvement is a TRIGGER TO INSPECT, not to stop: open the
|
|
264
|
+
verify-evidence diff images for the worst configs and look before
|
|
265
|
+
deciding (a measured run scored byte-identical twice, inspected
|
|
266
|
+
the diffs, and round 3 went 27/27 certified — a stop rule would
|
|
267
|
+
have shipped 5/27). Stop only after an inspection round produced
|
|
268
|
+
no fix hypothesis — then report the honest final state.
|
|
259
269
|
5. MODEL SELECTION — mechanical AND asked. `engine brief` and
|
|
260
270
|
`engine score` refuse to run without a declared model, so the
|
|
261
271
|
choice must be settled first. When a user is present, ask before
|
package/dist/tendril-mcp.js
CHANGED
|
@@ -37,7 +37,8 @@ var TOOLS = [
|
|
|
37
37
|
metadata: optStr("ONLY when get_metadata genuinely returned one single block: its text verbatim (otherwise use metadataParts)"),
|
|
38
38
|
metadataFiles: z.array(z.string()).optional().describe('saved verbatim get_metadata envelope file paths \u2014 JSON shape {"content":[{"type":"text","text":"<frame \u2026>"}]}; optionally <file>@<frameId>. Prefer metadataParts: no file to write'),
|
|
39
39
|
defaults: z.array(z.string()).optional().describe(`axis defaults as "Axis=Value" (from the user's defaultsToConfirm answers; may re-plan a set with nothing recorded yet)`),
|
|
40
|
-
componentSet: optStr("record only this component set (the user's pick from a multiple-component-sets error)")
|
|
40
|
+
componentSet: optStr("record only this component set (the user's pick from a multiple-component-sets error)"),
|
|
41
|
+
figmaFile: optStr("the Figma file key from the design URL you were handed \u2014 figma.com/design/<KEY>/\u2026, pass exactly <KEY>. ALWAYS pass it on a fresh plan: it is the set's recorded file identity for cross-bundle composition (ADR-013), captured at plan time only and never backfillable later")
|
|
41
42
|
}),
|
|
42
43
|
// Texts ride temp files, never argv: Windows caps a command line at
|
|
43
44
|
// ~32 KB and metadata envelopes can exceed it.
|
|
@@ -60,7 +61,11 @@ var TOOLS = [
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
if (argvOut.length === 6) throw new Error("nothing to plan from \u2014 pass metadataParts (normal), metadata (single-block), or metadataFiles");
|
|
63
|
-
argvOut.push(
|
|
64
|
+
argvOut.push(
|
|
65
|
+
...i["defaults"] !== void 0 ? ["--default", ...i["defaults"]] : [],
|
|
66
|
+
...i["componentSet"] !== void 0 ? ["--component-set", i["componentSet"]] : [],
|
|
67
|
+
...typeof i["figmaFile"] === "string" ? ["--figma-file", i["figmaFile"]] : []
|
|
68
|
+
);
|
|
64
69
|
return argvOut;
|
|
65
70
|
}
|
|
66
71
|
},
|
package/dist/tendril.js
CHANGED
|
@@ -1069,6 +1069,9 @@ function planSet(setDir, component, symbols, opts = {}) {
|
|
|
1069
1069
|
const manifest = {
|
|
1070
1070
|
version: 1,
|
|
1071
1071
|
component,
|
|
1072
|
+
...opts.figmaFile !== void 0 ? { figmaFile: opts.figmaFile } : {},
|
|
1073
|
+
...opts.componentSetNode !== void 0 ? { componentSetNode: opts.componentSetNode } : {},
|
|
1074
|
+
...opts.figmaComponentName !== void 0 ? { figmaComponentName: opts.figmaComponentName } : {},
|
|
1072
1075
|
...opts.sourceFrames !== void 0 ? { sourceFrames: opts.sourceFrames } : {},
|
|
1073
1076
|
...opts.variantScope !== void 0 ? { variantScope: opts.variantScope } : {},
|
|
1074
1077
|
...opts.defaults !== void 0 && Object.keys(opts.defaults).length > 0 ? { defaults: opts.defaults } : {},
|
|
@@ -1215,8 +1218,27 @@ var init_session = __esm({
|
|
|
1215
1218
|
SessionManifestSchema = z4.object({
|
|
1216
1219
|
version: z4.literal(1),
|
|
1217
1220
|
component: z4.string().min(1),
|
|
1221
|
+
/** ADR-013 slice 1 (v4, capture-only): cross-set identity, captured
|
|
1222
|
+
* at RECORD TIME on fresh plans only — NEVER backfilled (the
|
|
1223
|
+
* recording-set hash covers this file's bytes, so adding fields to
|
|
1224
|
+
* a set that already produced bundles voids their provenance).
|
|
1225
|
+
* `figmaFile` is the Figma file key from the URL the agent host was
|
|
1226
|
+
* handed — an AGENT ASSERTION, disclosed, never enforcement-bearing
|
|
1227
|
+
* alone (ADR-013 §1). `componentSetNode`/`figmaComponentName` are
|
|
1228
|
+
* the enclosing set's id and Figma-side name as the planner's own
|
|
1229
|
+
* metadata parse saw them — the identity that stops a re-recording
|
|
1230
|
+
* of the same component from reading as a different one (`component`
|
|
1231
|
+
* below is the plan-time DISPLAY name; the measured case recorded
|
|
1232
|
+
* one control as "Lucent Control (Dark)" and "control"). All
|
|
1233
|
+
* optional: legacy sets simply cannot join, disclosed. */
|
|
1234
|
+
figmaFile: z4.string().regex(/^[A-Za-z0-9]{8,64}$/).optional(),
|
|
1235
|
+
componentSetNode: z4.string().optional(),
|
|
1236
|
+
figmaComponentName: z4.string().optional(),
|
|
1218
1237
|
sourceFrames: z4.record(z4.string(), z4.string()).optional(),
|
|
1219
|
-
|
|
1238
|
+
/** Per-rep `componentKey` is SEEDED (ADR-013 v4: the one-call MCP
|
|
1239
|
+
* probe decides whether anything can ever fill it) so interim sets
|
|
1240
|
+
* are not born cross-file-dead if keys turn out to be available. */
|
|
1241
|
+
reps: z4.array(z4.object({ slug: z4.string(), nodeId: z4.string(), sourceFrame: z4.string().optional(), componentKey: z4.string().optional() })).min(1),
|
|
1220
1242
|
notRecorded: z4.string().optional(),
|
|
1221
1243
|
/** Explicit axis-default overrides from `record plan --default`.
|
|
1222
1244
|
* Recorded-truth-side on purpose: the default decides which pose an
|
|
@@ -2232,6 +2254,8 @@ async function runTokenLint(css, fileLabel = "generated.css", definedVars2, reco
|
|
|
2232
2254
|
property: "custom-property-ident",
|
|
2233
2255
|
message: `var(${name}) is not a legal custom property reference \u2014 the browser drops the whole declaration`
|
|
2234
2256
|
});
|
|
2257
|
+
} else if (name.startsWith("--tendril-")) {
|
|
2258
|
+
continue;
|
|
2235
2259
|
} else if (definedVars2 !== void 0 && !definedVars2.has(name) && !ownDefs.has(name)) {
|
|
2236
2260
|
violations.push({
|
|
2237
2261
|
file: fileLabel,
|
|
@@ -7871,10 +7895,10 @@ function symbolsFromMetadataEnvelope(file, sourceFrame) {
|
|
|
7871
7895
|
nodeId: node.id,
|
|
7872
7896
|
name: node.name,
|
|
7873
7897
|
...sourceFrame !== void 0 ? { sourceFrame } : {},
|
|
7874
|
-
...ancestor !== void 0 ? { setName: ancestor } : {}
|
|
7898
|
+
...ancestor !== void 0 ? { setName: ancestor.name, setNodeId: ancestor.id } : {}
|
|
7875
7899
|
});
|
|
7876
7900
|
}
|
|
7877
|
-
const next = node.type !== "COMPONENT" && node.name !== "" ? node.name : ancestor;
|
|
7901
|
+
const next = node.type !== "COMPONENT" && node.name !== "" ? { name: node.name, id: node.id } : ancestor;
|
|
7878
7902
|
for (const child of node.children) walk2(child, next);
|
|
7879
7903
|
};
|
|
7880
7904
|
const forest = parseMetadataForest(text);
|
|
@@ -8012,10 +8036,25 @@ function runRecordPlan(opts) {
|
|
|
8012
8036
|
if (metadataTruncated) {
|
|
8013
8037
|
warn(opts, "get_metadata response appears TRUNCATED (unclosed structure) \u2014 the variant list below may be incomplete. Cross-check variantsFound against the variant count Figma shows for this component set; re-fetch the metadata if lower.");
|
|
8014
8038
|
}
|
|
8039
|
+
if (opts.figmaFile !== void 0 && !/^[A-Za-z0-9]{8,64}$/.test(opts.figmaFile)) {
|
|
8040
|
+
fail(opts, ExitCode.InputValidation, {
|
|
8041
|
+
error: `--figma-file must be the bare file key from the Figma URL (letters and digits only) \u2014 got ${JSON.stringify(opts.figmaFile)}`,
|
|
8042
|
+
code: "bad-figma-file",
|
|
8043
|
+
remediation: "From https://www.figma.com/design/<KEY>/<name>?node-id=\u2026, pass exactly <KEY>."
|
|
8044
|
+
});
|
|
8045
|
+
}
|
|
8046
|
+
const setIdentity = (() => {
|
|
8047
|
+
if (variantScope !== "component-set") return {};
|
|
8048
|
+
const ids = [...new Set(symbols.map((s) => s.setNodeId).filter((x) => x !== void 0))];
|
|
8049
|
+
const names = [...new Set(symbols.map((s) => s.setName).filter((x) => x !== void 0))];
|
|
8050
|
+
return ids.length === 1 && names.length === 1 ? { componentSetNode: ids[0], figmaComponentName: names[0] } : {};
|
|
8051
|
+
})();
|
|
8015
8052
|
const { manifest, plan, resumed, toppedUp } = planSet(opts.setDir, opts.component, symbols, {
|
|
8016
8053
|
...Object.keys(defaults).length > 0 ? { defaults } : {},
|
|
8017
8054
|
...opts.sample === true ? { sample: true } : {},
|
|
8018
|
-
variantScope
|
|
8055
|
+
variantScope,
|
|
8056
|
+
...opts.figmaFile !== void 0 ? { figmaFile: opts.figmaFile } : {},
|
|
8057
|
+
...setIdentity
|
|
8019
8058
|
});
|
|
8020
8059
|
const defaultReports = reportAxisDefaults(symbols, Object.keys(defaults).length > 0 ? defaults : void 0);
|
|
8021
8060
|
const toConfirm = resumed ? [] : defaultReports.filter((r) => (r.rule === "non-interaction" || r.rule === "frequency") && r.domain.length > 1);
|
|
@@ -11146,14 +11185,15 @@ function latticeCoverage(setManifest, scoredConfigs) {
|
|
|
11146
11185
|
const lattice = setManifest.latticeNames?.length;
|
|
11147
11186
|
const established = setManifest.variantScope === "component-set";
|
|
11148
11187
|
const notRecorded = setManifest.notRecorded !== void 0 && setManifest.notRecorded !== "" ? { notRecorded: setManifest.notRecorded } : {};
|
|
11149
|
-
|
|
11188
|
+
const latticeUnestablished = setManifest.variantScope === "selection" ? "the recording was planned from a node SELECTION, not from a component set \u2014 the plan counted the nodes it was handed and could not see whether the set holds more variants, so the number of unrecorded poses is UNKNOWN, not zero" : setManifest.variantScope === "component-set" ? "the planner saw the component set and it declares no variant axes \u2014 the planned pose(s) are the whole set as Figma showed it, but there is no variant lattice to count" : "this set was planned before variant scope was recorded, so whether its lattice came from a component set or from a handed-in selection was never established \u2014 the number of unrecorded poses is unknown, not zero";
|
|
11189
|
+
if (lattice === void 0) return { latticeConfigs: null, latticeUnestablished, ...notRecorded };
|
|
11150
11190
|
if (established) return { latticeConfigs: lattice, unrecordedConfigs: Math.max(0, lattice - scoredConfigs), ...notRecorded };
|
|
11151
11191
|
return {
|
|
11152
11192
|
latticeConfigs: null,
|
|
11153
11193
|
// The count is still worth reporting — it is just not a
|
|
11154
11194
|
// denominator, and the name has to stop implying that it is.
|
|
11155
11195
|
variantsPlanned: lattice,
|
|
11156
|
-
latticeUnestablished
|
|
11196
|
+
latticeUnestablished,
|
|
11157
11197
|
...notRecorded
|
|
11158
11198
|
};
|
|
11159
11199
|
}
|
|
@@ -11557,8 +11597,10 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
11557
11597
|
process.stdout.write(`LATTICE all ${cov.latticeConfigs} poses of the component set's own lattice are recorded and scored \u2014 the denominator is the set's, not the recorded subset's
|
|
11558
11598
|
`);
|
|
11559
11599
|
} else if (cov.latticeConfigs === null) {
|
|
11560
|
-
process.stdout.write(
|
|
11561
|
-
`
|
|
11600
|
+
process.stdout.write(
|
|
11601
|
+
`COVERAGE denominator unknown \u2014 ${cov.latticeUnestablished ?? "set predates lattice tracking"} \u2014 scored configs are verified; completeness is not established
|
|
11602
|
+
`
|
|
11603
|
+
);
|
|
11562
11604
|
}
|
|
11563
11605
|
}
|
|
11564
11606
|
process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
|
|
@@ -11767,7 +11809,7 @@ ${segments}`;
|
|
|
11767
11809
|
`Run \`${tendrilCommand(
|
|
11768
11810
|
`engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"} --json`
|
|
11769
11811
|
)}\` \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.`,
|
|
11770
|
-
"Apply the returned feedback and re-score. Stop when all checks pass
|
|
11812
|
+
"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).",
|
|
11771
11813
|
"Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
|
|
11772
11814
|
]
|
|
11773
11815
|
},
|
|
@@ -12225,7 +12267,8 @@ var init_server = __esm({
|
|
|
12225
12267
|
metadata: optStr("ONLY when get_metadata genuinely returned one single block: its text verbatim (otherwise use metadataParts)"),
|
|
12226
12268
|
metadataFiles: z12.array(z12.string()).optional().describe('saved verbatim get_metadata envelope file paths \u2014 JSON shape {"content":[{"type":"text","text":"<frame \u2026>"}]}; optionally <file>@<frameId>. Prefer metadataParts: no file to write'),
|
|
12227
12269
|
defaults: z12.array(z12.string()).optional().describe(`axis defaults as "Axis=Value" (from the user's defaultsToConfirm answers; may re-plan a set with nothing recorded yet)`),
|
|
12228
|
-
componentSet: optStr("record only this component set (the user's pick from a multiple-component-sets error)")
|
|
12270
|
+
componentSet: optStr("record only this component set (the user's pick from a multiple-component-sets error)"),
|
|
12271
|
+
figmaFile: optStr("the Figma file key from the design URL you were handed \u2014 figma.com/design/<KEY>/\u2026, pass exactly <KEY>. ALWAYS pass it on a fresh plan: it is the set's recorded file identity for cross-bundle composition (ADR-013), captured at plan time only and never backfillable later")
|
|
12229
12272
|
}),
|
|
12230
12273
|
// Texts ride temp files, never argv: Windows caps a command line at
|
|
12231
12274
|
// ~32 KB and metadata envelopes can exceed it.
|
|
@@ -12248,7 +12291,11 @@ var init_server = __esm({
|
|
|
12248
12291
|
}
|
|
12249
12292
|
}
|
|
12250
12293
|
if (argvOut.length === 6) throw new Error("nothing to plan from \u2014 pass metadataParts (normal), metadata (single-block), or metadataFiles");
|
|
12251
|
-
argvOut.push(
|
|
12294
|
+
argvOut.push(
|
|
12295
|
+
...i["defaults"] !== void 0 ? ["--default", ...i["defaults"]] : [],
|
|
12296
|
+
...i["componentSet"] !== void 0 ? ["--component-set", i["componentSet"]] : [],
|
|
12297
|
+
...typeof i["figmaFile"] === "string" ? ["--figma-file", i["figmaFile"]] : []
|
|
12298
|
+
);
|
|
12252
12299
|
return argvOut;
|
|
12253
12300
|
}
|
|
12254
12301
|
},
|
|
@@ -14068,7 +14115,7 @@ function buildProgram() {
|
|
|
14068
14115
|
await runActivate2({ ...flags, serviceUrl: local["serviceUrl"] });
|
|
14069
14116
|
});
|
|
14070
14117
|
const record = program.command("record").description("Agent-driven recording protocol: plan the queue, get instructions, ingest verbatim envelopes, resume from disk.");
|
|
14071
|
-
record.command("plan").requiredOption("--set <dir>", "recording set directory").requiredOption("--component <name>", "component/system name").option("--metadata <file...>", "verbatim get_metadata envelope(s), optionally <file>@<frameId>").option("--metadata-raw-file <file>", "the get_metadata response TEXT exactly as received (single block); the CLI builds the envelope itself").option("--metadata-raw-parts-file <file>", "a JSON array of the get_metadata response blocks, each verbatim, in order (multi-block responses)").option("--default <axis=value...>", "explicit axis default, e.g. --default State=Rest (repeatable; persisted to the manifest; may re-plan an unrecorded set)").option("--component-set <name>", "when the metadata holds several component sets, record only this one (name = the set label in the plan error)").option("--sample", "cost sampling: anchor + one-factor + conflict crosses only (the FULL variant matrix is the default; sampling is blind to multi-axis interactions)").action(async (_o, cmd) => {
|
|
14118
|
+
record.command("plan").requiredOption("--set <dir>", "recording set directory").requiredOption("--component <name>", "component/system name").option("--metadata <file...>", "verbatim get_metadata envelope(s), optionally <file>@<frameId>").option("--metadata-raw-file <file>", "the get_metadata response TEXT exactly as received (single block); the CLI builds the envelope itself").option("--metadata-raw-parts-file <file>", "a JSON array of the get_metadata response blocks, each verbatim, in order (multi-block responses)").option("--default <axis=value...>", "explicit axis default, e.g. --default State=Rest (repeatable; persisted to the manifest; may re-plan an unrecorded set)").option("--component-set <name>", "when the metadata holds several component sets, record only this one (name = the set label in the plan error)").option("--sample", "cost sampling: anchor + one-factor + conflict crosses only (the FULL variant matrix is the default; sampling is blind to multi-axis interactions)").option("--figma-file <key>", "the Figma file key from the design URL (figma.com/design/<KEY>/\u2026) \u2014 recorded as the set's file identity for cross-bundle composition (ADR-013; fresh plans only, never backfilled)").action(async (_o, cmd) => {
|
|
14072
14119
|
const flags = globalFlags(cmd.parent.parent);
|
|
14073
14120
|
const local = cmd.opts();
|
|
14074
14121
|
const { runRecordPlan: runRecordPlan2 } = await Promise.resolve().then(() => (init_record(), record_exports));
|
|
@@ -14077,6 +14124,7 @@ function buildProgram() {
|
|
|
14077
14124
|
setDir: local["set"],
|
|
14078
14125
|
component: local["component"],
|
|
14079
14126
|
sample: local["sample"],
|
|
14127
|
+
...local["figmaFile"] !== void 0 ? { figmaFile: local["figmaFile"] } : {},
|
|
14080
14128
|
...local["metadata"] !== void 0 ? { metadataFiles: local["metadata"] } : {},
|
|
14081
14129
|
...local["metadataRawFile"] !== void 0 ? { metadataRawFile: local["metadataRawFile"] } : {},
|
|
14082
14130
|
...local["metadataRawPartsFile"] !== void 0 ? { metadataRawPartsFile: local["metadataRawPartsFile"] } : {},
|