@calibrate-ds/cli 0.1.88 → 0.1.90
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/README.md +2 -2
- package/dist/commands/init.js +1 -1
- package/dist/mcp/tools/action-tools.js +41 -22
- package/dist/mcp/tools/component-tools.js +37 -9
- package/dist/mcp/tools/info-tools.js +17 -0
- package/dist/mcp/tools/token-tools.js +3 -2
- package/dist/mcp/tools/verify-tool.js +12 -0
- package/dist/verify/run-verify.js +27 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -150,7 +150,7 @@ ptb stamp component Button # mark Button as implemented against the current desi
|
|
|
150
150
|
|
|
151
151
|
## MCP Server — AI IDE Integration
|
|
152
152
|
|
|
153
|
-
PTB exposes
|
|
153
|
+
PTB exposes 23 tools over a stdio MCP server. Your AI IDE can query component design context, run scans, generate code, and manage stamps — all from chat. Everything you can do in the terminal, you can do from chat.
|
|
154
154
|
|
|
155
155
|
### Available tools
|
|
156
156
|
|
|
@@ -768,6 +768,6 @@ See [PTB.md](./PTB.md) for architecture, type definitions, the generator interfa
|
|
|
768
768
|
```bash
|
|
769
769
|
pnpm install
|
|
770
770
|
pnpm build
|
|
771
|
-
pnpm test #
|
|
771
|
+
pnpm test # 609 tests across core, generator, and CLI
|
|
772
772
|
node apps/cli/dist/index.js --help
|
|
773
773
|
```
|
package/dist/commands/init.js
CHANGED
|
@@ -562,7 +562,7 @@ export function createInitCommand() {
|
|
|
562
562
|
console.log(` \x1b[36mptb export context\x1b[0m \x1b[2m— write .ptb/context/ files for AI IDEs (commit these)\x1b[0m`);
|
|
563
563
|
console.log(` \x1b[2mNext.js App Router? Add \x1b[0m\x1b[33m"useClient": true\x1b[0m\x1b[2m to the \x1b[0m\x1b[33mgeneration\x1b[0m\x1b[2m section of ptb.config.json\x1b[0m`);
|
|
564
564
|
console.log(` \x1b[2mso every generated component emits \x1b[0m\x1b[36m"use client"\x1b[0m\x1b[2m as its first line.\x1b[0m\n`);
|
|
565
|
-
console.log(` ${step++}. Connect your AI IDE via MCP — PTB exposes
|
|
565
|
+
console.log(` ${step++}. Connect your AI IDE via MCP — PTB exposes 23 tools (scan, generate, inspect, stamp, and more):\n`);
|
|
566
566
|
console.log(` \x1b[1mClaude Code:\x1b[0m already configured via \x1b[36m.mcp.json\x1b[0m — just restart Claude Code.\n`);
|
|
567
567
|
console.log(` \x1b[1mOther IDEs\x1b[0m (Cursor, Windsurf, Antigravity, VS Code):`);
|
|
568
568
|
console.log(` Once Claude Code is connected, ask in chat: \x1b[36m"set up MCP for cursor"\x1b[0m`);
|
|
@@ -280,15 +280,18 @@ criteria, and recommended build order.
|
|
|
280
280
|
|
|
281
281
|
TOKEN AUTHORITY — what to trust, what to verify:
|
|
282
282
|
- PTB's --ptb-* cssVar names are authoritative for token identity. Always emit them.
|
|
283
|
-
- If tokenConflicts is present
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
-
|
|
283
|
+
- If tokenConflicts is present, PTB's resolved token hex diverges from the raw scanned
|
|
284
|
+
fill. Each conflict includes rawValue — the hex the design actually renders, captured
|
|
285
|
+
by PTB's own scan. Trust rawValue as the visual ground truth and emit:
|
|
286
|
+
var(--ptb-token, <rawValue>) as the CSS fallback.
|
|
287
|
+
- For exact visual appearance, read the scan thumbnail at thumbnailPath (and per-variant
|
|
288
|
+
thumbnails) — it is PTB's captured ground truth for colors, spacing, and arrangement.
|
|
287
289
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
EVERYTHING YOU NEED IS IN PTB (no external tools required):
|
|
291
|
+
token names, dependency graph, variant axes, state/content contracts → read contextFile
|
|
292
|
+
exact rendered hex → tokenConflicts[].rawValue (from the scan)
|
|
293
|
+
visual arrangement/spacing → the scan thumbnail at thumbnailPath
|
|
294
|
+
hex → token name → get_token(hex)
|
|
292
295
|
|
|
293
296
|
TYPICAL WORKFLOW:
|
|
294
297
|
1. Call start_work("<name>") — read blockedBy and tokenConflicts first
|
|
@@ -296,6 +299,10 @@ TYPICAL WORKFLOW:
|
|
|
296
299
|
3. Read contextFile for the full implementation brief (it contains promptText, the
|
|
297
300
|
render tree, and variantDiffs — these are intentionally NOT inlined in this
|
|
298
301
|
response because they exceed the tool-call size limit on large components)
|
|
302
|
+
3b. For a multi-variant component set, call get_variant_tokens("<name>") for the
|
|
303
|
+
compact per-variant build matrix — every variant's resolved --ptb-* bindings,
|
|
304
|
+
colors, radius, padding, and gap in one small response. Prefer this over
|
|
305
|
+
excavating variantDiffs from the contextFile.
|
|
299
306
|
4. Implement the requested component
|
|
300
307
|
5. Call submit_work("<name>") when done`, { name: z.string().describe("Component to start working on, e.g. 'SearchBar'") }, async ({ name }) => {
|
|
301
308
|
let ctx;
|
|
@@ -398,11 +405,17 @@ TYPICAL WORKFLOW:
|
|
|
398
405
|
options: ax.options ?? [],
|
|
399
406
|
defaultValue: ax.defaultValue,
|
|
400
407
|
})),
|
|
408
|
+
// B-5: emit real binding data, not just slot names. The old map
|
|
409
|
+
// read b.token/b.cssVar/b.collection — fields that don't exist on
|
|
410
|
+
// a TokenBinding — so every entry serialized to just {slot}. The
|
|
411
|
+
// actual value (rawValue) and token identity live on resolvedToken.
|
|
401
412
|
tokenBindings: (context.tokenBindings ?? []).map((b) => ({
|
|
402
413
|
slot: b.slot,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
414
|
+
rawValue: b.rawValue ?? null,
|
|
415
|
+
token: b.resolvedToken?.name ?? b.boundToken?.name ?? null,
|
|
416
|
+
cssVar: b.resolvedToken?.cssVariable ?? null,
|
|
417
|
+
collection: b.resolvedToken?.collection?.name ?? null,
|
|
418
|
+
bound: !!b.boundToken,
|
|
406
419
|
})),
|
|
407
420
|
dependencies: context.dependencies.map((d) => ({
|
|
408
421
|
name: d.name,
|
|
@@ -421,8 +434,8 @@ TYPICAL WORKFLOW:
|
|
|
421
434
|
instructions: promptPackage.instructions,
|
|
422
435
|
structureSummary: promptPackage.structureSummary,
|
|
423
436
|
// Token conflicts: slots where PTB's resolved token hex differs from
|
|
424
|
-
// the raw
|
|
425
|
-
//
|
|
437
|
+
// the raw scanned fill. Each carries rawValue (what the design renders,
|
|
438
|
+
// from PTB's scan) — trust that and emit var(--ptb-token, <rawValue>).
|
|
426
439
|
...(context.tokenConflicts?.length
|
|
427
440
|
? { tokenConflicts: context.tokenConflicts }
|
|
428
441
|
: {}),
|
|
@@ -571,13 +584,13 @@ Returns the design context (variants, tokens, slots, contracts, render tree) for
|
|
|
571
584
|
component PLUS a dependency check: which child components this one uses and whether they are
|
|
572
585
|
already implemented in ptb.lock.
|
|
573
586
|
|
|
574
|
-
TOKEN AUTHORITY — what to trust
|
|
587
|
+
TOKEN AUTHORITY — what to trust:
|
|
575
588
|
- PTB's --ptb-* cssVar names are authoritative. Always use them in emitted CSS.
|
|
576
|
-
- If tokenConflicts is present, PTB's resolved token hex diverges from the raw
|
|
577
|
-
for those slots.
|
|
578
|
-
|
|
579
|
-
-
|
|
580
|
-
|
|
589
|
+
- If tokenConflicts is present, PTB's resolved token hex diverges from the raw scanned fill
|
|
590
|
+
for those slots. Each conflict carries rawValue — the hex the design actually renders,
|
|
591
|
+
from PTB's scan. Emit it as the literal fallback: var(--ptb-token, <rawValue>).
|
|
592
|
+
- For exact visual appearance — colors, spacing, image assets, arrangement — read the scan
|
|
593
|
+
thumbnail at thumbnailPath (and per-variant thumbnails). That is PTB's captured ground truth.
|
|
581
594
|
|
|
582
595
|
WORKFLOW — follow this exactly:
|
|
583
596
|
1. Check unbuiltDependencies. If any exist, call implement_component for each one in buildOrder first.
|
|
@@ -691,11 +704,17 @@ recursively when you call it for that dependency.`, { name: z.string().describe(
|
|
|
691
704
|
options: ax.options ?? [],
|
|
692
705
|
defaultValue: ax.defaultValue,
|
|
693
706
|
})),
|
|
707
|
+
// B-5: emit real binding data, not just slot names. The old map
|
|
708
|
+
// read b.token/b.cssVar/b.collection — fields that don't exist on
|
|
709
|
+
// a TokenBinding — so every entry serialized to just {slot}. The
|
|
710
|
+
// actual value (rawValue) and token identity live on resolvedToken.
|
|
694
711
|
tokenBindings: (context.tokenBindings ?? []).map((b) => ({
|
|
695
712
|
slot: b.slot,
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
713
|
+
rawValue: b.rawValue ?? null,
|
|
714
|
+
token: b.resolvedToken?.name ?? b.boundToken?.name ?? null,
|
|
715
|
+
cssVar: b.resolvedToken?.cssVariable ?? null,
|
|
716
|
+
collection: b.resolvedToken?.collection?.name ?? null,
|
|
717
|
+
bound: !!b.boundToken,
|
|
699
718
|
})),
|
|
700
719
|
dependencies: context.dependencies.map((d) => ({
|
|
701
720
|
name: d.name,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { getComponentContext } from "@calibrate-ds/core";
|
|
4
|
+
import { getComponentContext, buildVariantTokenMap } from "@calibrate-ds/core";
|
|
5
5
|
function toSlug(name) {
|
|
6
6
|
return name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7
7
|
}
|
|
@@ -51,16 +51,17 @@ WHAT PTB IS AUTHORITATIVE FOR (trust these without Figma):
|
|
|
51
51
|
- Variant axes, state contract, interaction contract, slot contract
|
|
52
52
|
- Lifecycle: designHash, stampedHash, workStatus (from ptb.lock)
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
- Exact visual appearance —
|
|
54
|
+
VISUAL GROUND TRUTH (all from PTB's own scan — no external tools needed):
|
|
55
|
+
- Exact visual appearance — read the scan thumbnail at thumbnailPath (and per-variant
|
|
56
|
+
thumbnails). That is the captured reference for colors, spacing, positions, and assets.
|
|
56
57
|
- tokenConflicts (if present): slots where PTB's resolved token hex diverges from the raw
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
scanned fill. Each carries rawValue — the hex the design actually renders. Emit PTB's
|
|
59
|
+
cssVar name with rawValue as the literal fallback: var(--ptb-token, <rawValue>).
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
EVERYTHING YOU NEED IS IN PTB:
|
|
62
|
+
token names, dependency graph, variant axes, state/content contracts → this response
|
|
63
|
+
exact rendered hex → tokenConflicts[].rawValue visual arrangement → thumbnailPath
|
|
64
|
+
hex → token name → get_token(hex)`, {
|
|
64
65
|
name: z.string().describe("Component display name, e.g. 'Button' or 'Notification Item'"),
|
|
65
66
|
detail: z.enum(["summary", "full"]).optional().default("summary").describe("'summary' (default): compact planning brief — variant axes, token bindings, dependencies, layout, state axis names, contextFile path. " +
|
|
66
67
|
"'full': complete context with render tree and compressed variant patches."),
|
|
@@ -200,6 +201,33 @@ DIVISION OF LABOR WITH FIGMA MCP (composite workflow):
|
|
|
200
201
|
catch { /* non-fatal */ }
|
|
201
202
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
202
203
|
});
|
|
204
|
+
server.tool("get_variant_tokens", `Get the compact per-variant token/style build matrix for a component set.
|
|
205
|
+
|
|
206
|
+
For EVERY variant (all combinations of the variant axes), returns the resolved
|
|
207
|
+
design-system bindings and spacing needed to implement it:
|
|
208
|
+
- background / text-color / border-color → { cssVar (--ptb-*), value (hex), token }
|
|
209
|
+
- border-radius, padding, gap → resolved values (px)
|
|
210
|
+
- per-node text content, stroke weight + align (inside/outside)
|
|
211
|
+
|
|
212
|
+
This is the data an agent needs to build all variants WITHOUT reading the large
|
|
213
|
+
context render tree or reconstructing it from variantDiffs. Emit each slot as
|
|
214
|
+
var(<cssVar>, <value>) when cssVar is present; use value directly when it is not.
|
|
215
|
+
Returns variantCount:0 with a note for single (non-set) components.`, { name: z.string().describe("Component set name, e.g. 'ChipsWeb'") }, async ({ name }) => {
|
|
216
|
+
let ctx;
|
|
217
|
+
try {
|
|
218
|
+
ctx = await getContext();
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
return { content: [{ type: "text", text: e.message }] };
|
|
222
|
+
}
|
|
223
|
+
const comp = findComponent(ctx.snapshot, name);
|
|
224
|
+
if (!comp) {
|
|
225
|
+
const available = ctx.snapshot.components.map((c) => c.name).slice(0, 15).join(", ");
|
|
226
|
+
return { content: [{ type: "text", text: `Component '${name}' not found.\nAvailable (first 15): ${available}` }] };
|
|
227
|
+
}
|
|
228
|
+
const map = buildVariantTokenMap(comp);
|
|
229
|
+
return { content: [{ type: "text", text: JSON.stringify(map, null, 2) }] };
|
|
230
|
+
});
|
|
203
231
|
},
|
|
204
232
|
};
|
|
205
233
|
//# sourceMappingURL=component-tools.js.map
|
|
@@ -3,6 +3,23 @@ import * as path from "node:path";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { resolveToken } from "@calibrate-ds/core";
|
|
5
5
|
const UPGRADE_CHECKLIST = {
|
|
6
|
+
"0.1.90": [
|
|
7
|
+
"BAKE-OFF audit fixes (docs/PTB_VS_FIGMA_BAKEOFF.md). A cold PTB-only vs Figma-only head-to-head exposed that PTB's verifier declined to enforce the one thing PTB uniquely provides: design-system token binding.",
|
|
8
|
+
"FIX (B-1, the moat gate): verify computed cssVarScore (fraction of the design's bound --ptb-* vars present in the CSS) then NEVER read it — a token-blind component that renders correctly but binds ZERO design tokens PASSED. cssVarScore is now a HARD GATE: cssVarScore === 0 → fail, and tokenScore cannot rescue it. cssVarScore is now surfaced in the run_verify / get_verify_report responses.",
|
|
9
|
+
"FIX (B-1, naming): tokenScore was misnamed — it measures rendered TEXT content, not token binding. The response now also returns textContentScore (the accurate name); tokenScore is kept as a deprecated alias so nothing breaks.",
|
|
10
|
+
"FIX (B-2, scaffold CSS): a token-bound border emitted 'border: var(--x, #hex)' — a bare color with no width/style, which is INVALID and dropped, leaving outlined components with no border. Now emits 'border: 1px solid var(...)'. Also: slots that map to the same CSS property (text/fill/iconColor → color) no longer emit a duplicate declaration. Regenerate components (generate-components --force) to pick these up.",
|
|
11
|
+
"FIX (B-5): start_work's tokenBindings returned just {slot} with no values (it read fields that don't exist on the type). It now returns rawValue, token name, cssVar, collection, and bound flag.",
|
|
12
|
+
"NEW TOOL (B-3, the cost win): get_variant_tokens(name) returns a compact PRE-RESOLVED per-variant token matrix — for every variant, the --ptb-* binding + value for background/text/border, plus radius/padding/gap/text and stroke weight+align. No more scripting through variantDiffs: ChipsWeb's full 42-variant matrix is ~6k tokens (vs ~86k to read context naively). start_work now points agents to it for multi-variant sets. Restart your MCP server to see the new tool.",
|
|
13
|
+
"FIX (B-4): Storybook loaded no fonts, so text rendered in a fallback and CAPPED every text component's pixel score (~0.82) regardless of correctness. setup now writes .storybook/preview-head.html loading the design's fonts (from typography.css) via Google Fonts. Re-run Storybook setup / ptb init to get it.",
|
|
14
|
+
"FIX (B-8): per-variant verify now names how many variants had no reference thumbnail and were skipped (was silently undercounting). FIX (B-9): the primary diff image is retained + its path kept in the verify report, so a low score is inspectable (was deleting all pngs).",
|
|
15
|
+
"B-7 strokeAlign is now available to the agent via get_variant_tokens (weight+align). Deferred (verified, not changed): B-2 double-padding, B-6 radius alias tier (risky core change), B-10 unbound raw hexes (design-side, not a PTB bug). Restart MCP; regenerate components for the CSS fixes. 609 tests.",
|
|
16
|
+
],
|
|
17
|
+
"0.1.89": [
|
|
18
|
+
"INDEPENDENCE release — PTB no longer recommends or depends on any external tool. Previously several MCP tool descriptions told the agent to 'cross-check with Figma MCP' (get_variable_defs / get_screenshot) for exact hex and visual truth.",
|
|
19
|
+
"That guidance was redundant: PTB already has that data from its own scan. tokenConflicts now direct the agent to rawValue — the hex the design actually renders, captured by the scan — with var(--ptb-token, <rawValue>) as the fallback; and the scan thumbnail at thumbnailPath is the visual ground truth for colors/spacing/arrangement.",
|
|
20
|
+
"Net effect: fewer round-trips, no dependency on a third-party MCP, and the agent gets a concrete value instead of being sent to another tool. No code behavior changed — only agent-facing guidance in start_work / get_component / get_token descriptions and two type comments.",
|
|
21
|
+
"Restart your IDE's MCP server to pick up the updated tool descriptions. No re-scan. 593 tests.",
|
|
22
|
+
],
|
|
6
23
|
"0.1.88": [
|
|
7
24
|
"0.1.87 audit fixes (docs/PTB_0187_REPORT.md). Closes the last functional beta blocker plus the emitter-parity residuals. Six-plus releases, zero regressions.",
|
|
8
25
|
"FIX (NEW-8, open since .79 — was FAILING HARD): start_work's MCP response inlined promptText (100 KB+, 94.7% of the payload), so start_work on a large component-set EXCEEDED the tool-call token limit and the call failed outright. promptText is no longer inlined in the response. The contextFile on disk is UNCHANGED — it still contains promptText and every detail; read the full brief from there (the tool description says so). This unblocks the primary agent entry point.",
|
|
@@ -141,8 +141,9 @@ Accepts:
|
|
|
141
141
|
|
|
142
142
|
Returns: collection, tokenName, cssVar (--ptb-* variable), resolvedValue, aliasChain (when present).
|
|
143
143
|
|
|
144
|
-
PTB owns the token identity map — use hex lookup when
|
|
145
|
-
|
|
144
|
+
PTB owns the token identity map — use hex lookup when you have a raw color (e.g. a
|
|
145
|
+
tokenConflicts rawValue from the scan) and need the canonical --ptb-* name instead of
|
|
146
|
+
guessing the convention.`, { query: z.string().describe("Token name, path, Figma variable name (surface/surface-info-light), or hex color (#E5F3FF)") }, async ({ query }) => {
|
|
146
147
|
let ctx;
|
|
147
148
|
try {
|
|
148
149
|
ctx = await getContext();
|
|
@@ -86,6 +86,16 @@ Returns: pixel match score, AI assessment ("matches" / "minor-differences" / "si
|
|
|
86
86
|
passFail,
|
|
87
87
|
verdictScore,
|
|
88
88
|
verdictMetric,
|
|
89
|
+
// B-1: cssVarScore is the design-system BINDING signal — fraction
|
|
90
|
+
// of the design's bound --ptb-* vars present in the CSS. 0 = the
|
|
91
|
+
// component renders correctly but is disconnected from the design
|
|
92
|
+
// system (hard fail via the verdict gate). Surfaced so agents/users
|
|
93
|
+
// can see it, not just its warning string.
|
|
94
|
+
cssVarScore: result.cssVarScore ?? null,
|
|
95
|
+
// textContentScore is the accurate name for what tokenScore measures
|
|
96
|
+
// (rendered TEXT content, NOT token binding). tokenScore is a
|
|
97
|
+
// deprecated alias kept for existing consumers.
|
|
98
|
+
textContentScore: result.textContentScore ?? result.tokenScore ?? null,
|
|
89
99
|
tokenScore: result.tokenScore ?? null,
|
|
90
100
|
structureScore: result.structureScore ?? null,
|
|
91
101
|
pixelMatchScore: score,
|
|
@@ -122,6 +132,8 @@ Returns: pixel match score, AI assessment ("matches" / "minor-differences" / "si
|
|
|
122
132
|
const summary = {
|
|
123
133
|
component: report.componentName,
|
|
124
134
|
checkedAt: report.checkedAt,
|
|
135
|
+
cssVarScore: report.cssVarScore ?? null,
|
|
136
|
+
textContentScore: report.textContentScore ?? report.tokenScore ?? null,
|
|
125
137
|
pixelMatchScore: report.pixelMatch?.score ?? null,
|
|
126
138
|
assessment: report.aiFeedback?.overallAssessment ?? null,
|
|
127
139
|
summary: report.aiFeedback?.summary ?? null,
|
|
@@ -493,6 +493,17 @@ export async function runVerify(component, config, snapshot, options = {}) {
|
|
|
493
493
|
// warning gate can check whether per-variant thumbnails are available before
|
|
494
494
|
// deciding whether to fire a "no design reference" warning.
|
|
495
495
|
const variantThumbs = component.variantThumbnails ?? [];
|
|
496
|
+
// B-8: the true number of variants in the set (every variant child), which can
|
|
497
|
+
// exceed the number of reference thumbnails when some variants failed to export.
|
|
498
|
+
// Previously the "capped at N of M" message used only the thumbnail count, so
|
|
499
|
+
// missing variants were silently unaccounted for. Surface the real gap.
|
|
500
|
+
const declaredVariantCount = component.variantNodeRefs?.length ??
|
|
501
|
+
component.variantSnapshots?.length ??
|
|
502
|
+
variantThumbs.length;
|
|
503
|
+
if (declaredVariantCount > variantThumbs.length) {
|
|
504
|
+
warnings.push(`${declaredVariantCount - variantThumbs.length} of ${declaredVariantCount} variants have no reference ` +
|
|
505
|
+
`thumbnail and were not pixel-verified (re-run 'ptb scan' to regenerate variant references).`);
|
|
506
|
+
}
|
|
496
507
|
// GAP-022: prefer the single-variant reference for pixel-diff (a single-variant
|
|
497
508
|
// story can't match the full variant grid). Fall back to the grid thumbnail.
|
|
498
509
|
const thumbnailPath = component.variantThumbnailPath ?? component.thumbnailPath ?? null;
|
|
@@ -621,6 +632,9 @@ export async function runVerify(component, config, snapshot, options = {}) {
|
|
|
621
632
|
report.designTokenCount = designTokens.length;
|
|
622
633
|
report.foundTokenCount = found.length;
|
|
623
634
|
report.tokenScore = parseFloat((found.length / designTokens.length).toFixed(4));
|
|
635
|
+
// B-1: same value under the accurate name (tokenScore measures
|
|
636
|
+
// rendered TEXT content, not token binding). tokenScore kept as alias.
|
|
637
|
+
report.textContentScore = report.tokenScore;
|
|
624
638
|
}
|
|
625
639
|
}
|
|
626
640
|
}
|
|
@@ -756,14 +770,23 @@ export async function runVerify(component, config, snapshot, options = {}) {
|
|
|
756
770
|
}
|
|
757
771
|
}
|
|
758
772
|
// ── Step 5: Cleanup PNG artifacts ───────────────────────────────────────
|
|
759
|
-
//
|
|
760
|
-
//
|
|
773
|
+
// B-9: retain the single primary diff image so a low score is inspectable —
|
|
774
|
+
// previously ALL pngs were deleted and screenshots.diff nulled, so an agent
|
|
775
|
+
// seeing a low pixelMatch had no way to see WHERE it diverged (it had to find
|
|
776
|
+
// report.json on disk and re-run Playwright by hand). The figma/impl and
|
|
777
|
+
// per-variant pngs are still removed; one diff per component is negligible disk.
|
|
761
778
|
// Thumbnails live in .ptb/thumbnails/ — outside verifyDir — and are not touched.
|
|
762
|
-
report.screenshots
|
|
779
|
+
const keepDiff = report.screenshots.diff ? path.basename(report.screenshots.diff) : null;
|
|
780
|
+
report.screenshots = {
|
|
781
|
+
figma: null,
|
|
782
|
+
figmaSource: report.screenshots.figmaSource,
|
|
783
|
+
impl: null,
|
|
784
|
+
diff: report.screenshots.diff ?? null,
|
|
785
|
+
};
|
|
763
786
|
// Persist report
|
|
764
787
|
const reportPath = path.join(verifyDir, "report.json");
|
|
765
788
|
await fs.writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
766
|
-
const pngs = (await fs.readdir(verifyDir)).filter(e => e.endsWith(".png"));
|
|
789
|
+
const pngs = (await fs.readdir(verifyDir)).filter(e => e.endsWith(".png") && e !== keepDiff);
|
|
767
790
|
await Promise.all(pngs.map(e => fs.unlink(path.join(verifyDir, e)).catch(() => undefined)));
|
|
768
791
|
return { ...report, warnings };
|
|
769
792
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calibrate-ds/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.90",
|
|
4
4
|
"license": "FSL-1.1-MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"commander": "^14.0.3",
|
|
45
45
|
"openai": "^6.34.0",
|
|
46
46
|
"zod": "^4.3.6",
|
|
47
|
-
"@calibrate-ds/core": "^0.1.
|
|
48
|
-
"@calibrate-ds/figma-client": "^0.1.
|
|
49
|
-
"@calibrate-ds/generator-react": "^0.1.
|
|
50
|
-
"@calibrate-ds/shared-types": "^0.1.
|
|
47
|
+
"@calibrate-ds/core": "^0.1.90",
|
|
48
|
+
"@calibrate-ds/figma-client": "^0.1.90",
|
|
49
|
+
"@calibrate-ds/generator-react": "^0.1.90",
|
|
50
|
+
"@calibrate-ds/shared-types": "^0.1.90"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "^22.13.5",
|