raffina 0.2.0
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/LICENSE +21 -0
- package/README.md +129 -0
- package/bin/appIcon-cqr4n4ty.png +0 -0
- package/bin/cli.js +12563 -0
- package/docs/verification.md +500 -0
- package/package.json +39 -0
- package/skills/raffina-design-loop/SKILL.md +138 -0
- package/skills/raffina-design-loop/agents/openai.yaml +4 -0
- package/skills/raffina-design-loop/references/cli-contract.md +197 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
# Raffina verifier reference
|
|
2
|
+
|
|
3
|
+
This document describes the verifier implemented in Raffina `0.2.0` and report schema `1.0.0`. It is a code-level reference for people integrating Raffina, interpreting a score, or building an agent loop around its output.
|
|
4
|
+
|
|
5
|
+
The source of truth is [`packages/core/src/verify.ts`](../packages/core/src/verify.ts) and the eight `verify-*.ts` category modules beside it.
|
|
6
|
+
|
|
7
|
+
## What verification does
|
|
8
|
+
|
|
9
|
+
Given a previously extracted brand system and a target, Raffina:
|
|
10
|
+
|
|
11
|
+
1. renders the target at an explicit viewport;
|
|
12
|
+
2. observes the same evidence families collected during extraction;
|
|
13
|
+
3. selects the reference capture for that exact viewport and default state;
|
|
14
|
+
4. runs eight category verifiers;
|
|
15
|
+
5. combines their scores according to the selected profile;
|
|
16
|
+
6. calculates evidence coverage and load-bearing gate failures;
|
|
17
|
+
7. returns a verdict, findings, caveats, and up to five ranked repairs.
|
|
18
|
+
|
|
19
|
+
For a URL or local HTML file, the target observation contains:
|
|
20
|
+
|
|
21
|
+
- body colors and typography;
|
|
22
|
+
- counted colors, backgrounds, fonts, font sizes, radii, borders, shadows, spacing, and content widths;
|
|
23
|
+
- heading styles and action copy;
|
|
24
|
+
- loaded and unloaded font faces;
|
|
25
|
+
- images, videos, icons, inline SVGs, backgrounds, and available SHA-256 fingerprints;
|
|
26
|
+
- animations, transitions, keyframes, and reduced-motion behavior;
|
|
27
|
+
- component-style snapshots and large layout regions;
|
|
28
|
+
- a regional screenshot fingerprint and composition-density map;
|
|
29
|
+
- capture-state evidence for consent, locale, and modal overlays.
|
|
30
|
+
|
|
31
|
+
The verifier does not compare an arbitrary target against a generic desktop average. Screenshot, composition, color, typography, assets, motion, shape, components, and voice use viewport-scoped evidence when it is available.
|
|
32
|
+
|
|
33
|
+
## Check result states
|
|
34
|
+
|
|
35
|
+
Every category returns:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
{
|
|
39
|
+
score: number; // 0–100
|
|
40
|
+
status: "measured" | "partial" | "not-measured";
|
|
41
|
+
summary: string;
|
|
42
|
+
details: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The status affects evidence coverage:
|
|
47
|
+
|
|
48
|
+
| Status | Coverage factor | Meaning |
|
|
49
|
+
| --- | ---: | --- |
|
|
50
|
+
| `measured` | 1.0 | The required reference and target evidence was available. |
|
|
51
|
+
| `partial` | 0.5 | The check is useful but intrinsically incomplete. Voice is currently partial. |
|
|
52
|
+
| `not-measured` | 0.0 | Evidence was missing, invalid, or incompatible. This is not a pass. |
|
|
53
|
+
|
|
54
|
+
Scores are rounded to whole numbers and clamped to `0–100`.
|
|
55
|
+
|
|
56
|
+
## Category weights
|
|
57
|
+
|
|
58
|
+
The default `brand` profile uses all eight categories:
|
|
59
|
+
|
|
60
|
+
| Category | Overall weight | Load-bearing minimum |
|
|
61
|
+
| --- | ---: | ---: |
|
|
62
|
+
| Visual | 30% | 80 |
|
|
63
|
+
| Composition | 15% | 70 |
|
|
64
|
+
| Color | 12% | 65 |
|
|
65
|
+
| Typography | 13% | 75 |
|
|
66
|
+
| Assets | 10% | 70 when fingerprinted reference assets exist |
|
|
67
|
+
| Motion | 7% | 65 when motion is measured |
|
|
68
|
+
| Shape | 5% | 65 when shape is measured |
|
|
69
|
+
| Voice | 8% | No separate gate |
|
|
70
|
+
|
|
71
|
+
The `visual-system` profile includes visual, composition, color, typography, motion, and shape. Asset identity and voice are still calculated and returned as diagnostics, but they are excluded from the score, verdict, findings, action order, and gates. The remaining weights are re-normalized across their active total of `0.82`.
|
|
72
|
+
|
|
73
|
+
## Overall score, coverage, and verdict
|
|
74
|
+
|
|
75
|
+
For the active profile:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
weighted score = Σ(category score × category weight) / Σ(active weights)
|
|
79
|
+
|
|
80
|
+
coverage = Σ(category weight × status factor) / Σ(active weights)
|
|
81
|
+
|
|
82
|
+
available-evidence score =
|
|
83
|
+
Σ(category score × category weight × status factor)
|
|
84
|
+
/ Σ(category weight × status factor)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
When coverage is at least `0.8`, Raffina uses the weighted score. Below `0.8`, it reports the available-evidence score so missing evidence is not silently converted into zeros—but the result cannot be `on-brand`.
|
|
88
|
+
|
|
89
|
+
After scoring, the load-bearing gates are evaluated. Any failed gate caps the displayed score at `84`, even when the weighted average is higher. This prevents a strong palette or voice score from hiding a structural, typographic, motion, or identity failure.
|
|
90
|
+
|
|
91
|
+
Verdicts are assigned exactly as follows:
|
|
92
|
+
|
|
93
|
+
| Condition | Verdict |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| Coverage is `0` | `needs-review` |
|
|
96
|
+
| Score is at least `85`, coverage is at least `0.8`, and every applicable gate passes | `on-brand` |
|
|
97
|
+
| Otherwise, score is at least `60` | `needs-review` |
|
|
98
|
+
| Otherwise | `off-brand` |
|
|
99
|
+
|
|
100
|
+
Finding severity is separate from the final verdict:
|
|
101
|
+
|
|
102
|
+
- `pass` at `85+`;
|
|
103
|
+
- `warning` at `65–84`;
|
|
104
|
+
- `fail` below `65`;
|
|
105
|
+
- a `not-measured` check is always a warning.
|
|
106
|
+
|
|
107
|
+
## Capture-state guard
|
|
108
|
+
|
|
109
|
+
Raffina refuses to score a target capture that is interrupted by a consent overlay, locale selector, or modal. Every category becomes `not-measured`, coverage and score become `0`, the verdict becomes `needs-review`, and the first action asks the caller to reach the intended canonical state without accepting optional tracking.
|
|
110
|
+
|
|
111
|
+
This guard runs before all category verifiers. A visually plausible screenshot contaminated by an overlay is never treated as brand truth.
|
|
112
|
+
|
|
113
|
+
## 1. Visual verifier
|
|
114
|
+
|
|
115
|
+
Source: [`verify-visual.ts`](../packages/core/src/verify-visual.ts)
|
|
116
|
+
|
|
117
|
+
### Evidence
|
|
118
|
+
|
|
119
|
+
- the exact default-state screenshot fingerprint for the target viewport;
|
|
120
|
+
- a fixed regional grid of RGB color, luma, contrast, and edge density;
|
|
121
|
+
- full luma and edge maps;
|
|
122
|
+
- rendered image width and height.
|
|
123
|
+
|
|
124
|
+
The check becomes `not-measured` when the viewport has no reference, dimensions differ, grid dimensions differ, or any fingerprint map is incomplete.
|
|
125
|
+
|
|
126
|
+
### Score
|
|
127
|
+
|
|
128
|
+
For every grid cell, Raffina calculates normalized RGB distance plus absolute luma, contrast, and edge-density error. It then calculates luma-map Pearson correlation across the complete screenshot.
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
visual =
|
|
132
|
+
similarity(mean cell RGB distance, sensitivity 5) × 22%
|
|
133
|
+
+ similarity(luma-map MAE, sensitivity 6) × 22%
|
|
134
|
+
+ similarity(edge-map MAE, sensitivity 7) × 18%
|
|
135
|
+
+ similarity(mean contrast error, sensitivity 7) × 13%
|
|
136
|
+
+ max(0, luma Pearson correlation) × 25%
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`similarity(distance, sensitivity)` is `exp(-distance × sensitivity)`.
|
|
140
|
+
|
|
141
|
+
The report retains the three worst grid regions, including target→reference color, luma, contrast, edge density, and normalized page coordinates. Repair output uses those regions to say where the rendered page diverges.
|
|
142
|
+
|
|
143
|
+
## 2. Composition verifier
|
|
144
|
+
|
|
145
|
+
Source: [`verify-composition.ts`](../packages/core/src/verify-composition.ts)
|
|
146
|
+
|
|
147
|
+
### Evidence
|
|
148
|
+
|
|
149
|
+
- regional element, text, and asset density maps;
|
|
150
|
+
- screenshot luma and edge structure;
|
|
151
|
+
- counted spacing and content-width values;
|
|
152
|
+
- component roles with background, foreground, border, radius, shadow, padding, and typography;
|
|
153
|
+
- salient large regions with bounds, position, z-index, clipping, background, radius, and shadow.
|
|
154
|
+
|
|
155
|
+
Like the visual check, composition requires an exact viewport, compatible dimensions, compatible grid shape, and complete maps.
|
|
156
|
+
|
|
157
|
+
### Density and rendered structure
|
|
158
|
+
|
|
159
|
+
Each density map is scored as:
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
map score = max(0, cosine similarity) × 55%
|
|
163
|
+
+ similarity(MAE, sensitivity 5) × 45%
|
|
164
|
+
|
|
165
|
+
spatial = elements × 40% + text × 35% + assets × 25%
|
|
166
|
+
|
|
167
|
+
rendered structure = max(0, luma Pearson correlation) × 65%
|
|
168
|
+
+ similarity(edge MAE, sensitivity 7) × 35%
|
|
169
|
+
|
|
170
|
+
rendered base = spatial × 50% + rendered structure × 50%
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
If valid visual maps are unavailable, `rendered structure` falls back to `spatial`.
|
|
174
|
+
|
|
175
|
+
### Supporting grammars
|
|
176
|
+
|
|
177
|
+
The rendered base is reduced—never increased—by supporting evidence in this order:
|
|
178
|
+
|
|
179
|
+
1. spacing-token grammar, 20% penalty weight;
|
|
180
|
+
2. content-width grammar, 35%;
|
|
181
|
+
3. component-role grammar, 20%;
|
|
182
|
+
4. layered layout-region geometry, 25%.
|
|
183
|
+
|
|
184
|
+
The penalty function is:
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
supportingPenalty(base, evidence, weight) =
|
|
188
|
+
evidence is absent ? base
|
|
189
|
+
: min(base, base × (1 - weight) + evidence × weight)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This prevents copying spacing tokens or component colors from compensating for a wrong hero layout.
|
|
193
|
+
|
|
194
|
+
Component grammar uses Jaccard set similarity for seven properties within each role, then averages across roles. Consent-related components are excluded. Transparent and translucent colors retain alpha whenever either side uses alpha.
|
|
195
|
+
|
|
196
|
+
Layout-region matching considers only salient reference regions—positioned, layered, or shadowed surfaces. Geometry compares normalized `x`, `y`, width, and height. Quality is geometry multiplied by `70% + feature coverage × 20% + role match × 10%`. Candidate pairs below `0.45` geometry are ignored, the globally optimal one-to-one assignment is selected, and a region counts as matched at `0.65` quality.
|
|
197
|
+
|
|
198
|
+
The report includes the three worst density regions, weakest component role and property scores, unmatched layout regions, closest candidates, and the selected global assignments.
|
|
199
|
+
|
|
200
|
+
## 3. Color verifier
|
|
201
|
+
|
|
202
|
+
Source: [`verify-color.ts`](../packages/core/src/verify-color.ts)
|
|
203
|
+
|
|
204
|
+
### Evidence
|
|
205
|
+
|
|
206
|
+
- viewport-scoped semantic roles when available;
|
|
207
|
+
- the 24 highest-occurrence reference palette rows;
|
|
208
|
+
- generated neutral tonal steps between semantic ink and canvas;
|
|
209
|
+
- the 30 highest-occurrence parseable target foreground and background colors.
|
|
210
|
+
|
|
211
|
+
CSS colors are parsed and compared in OKLab. Target observations are weighted by rendered occurrence count.
|
|
212
|
+
|
|
213
|
+
### Score
|
|
214
|
+
|
|
215
|
+
Raffina calculates two directions:
|
|
216
|
+
|
|
217
|
+
- **target precision:** how close rendered target colors are to the reference candidates;
|
|
218
|
+
- **reference coverage:** whether the required reference palette and semantic roles appear in the target.
|
|
219
|
+
|
|
220
|
+
Both convert weighted mean OKLab distance to a score using `100 × exp(-distance × 8)`.
|
|
221
|
+
|
|
222
|
+
If reference coverage is below `85`, the intermediate bidirectional score is `75% target precision + 25% reference coverage`. The final score cannot exceed:
|
|
223
|
+
|
|
224
|
+
- the target-precision score;
|
|
225
|
+
- the bidirectional score;
|
|
226
|
+
- `64` when reference coverage is below `50`;
|
|
227
|
+
- `64` when fewer than `50%` of rendered color occurrences are parseable.
|
|
228
|
+
|
|
229
|
+
Low-saturation neutral reference colors may match another low-saturation rendered target color so an evidenced tonal scale is not penalized for small neutral shifts.
|
|
230
|
+
|
|
231
|
+
The repair output identifies missing reference colors or the target color with the largest `distance × rendered share` contribution.
|
|
232
|
+
|
|
233
|
+
## 4. Typography verifier
|
|
234
|
+
|
|
235
|
+
Source: [`verify-typography.ts`](../packages/core/src/verify-typography.ts)
|
|
236
|
+
|
|
237
|
+
### Evidence
|
|
238
|
+
|
|
239
|
+
- reference body family and viewport-scoped heading roles;
|
|
240
|
+
- body, heading, and whole-page font usage;
|
|
241
|
+
- heading sizes, weights, line heights, and letter spacing;
|
|
242
|
+
- declared font faces, actual load status, and SHA-256 font fingerprints.
|
|
243
|
+
|
|
244
|
+
The body family must lead the rendered CSS stack. A differently named local family can count as equivalent only when its loaded bytes uniquely match one reference family. An ambiguous fingerprint alias is recorded but not trusted.
|
|
245
|
+
|
|
246
|
+
### Score
|
|
247
|
+
|
|
248
|
+
Declared use combines available evidence and re-normalizes missing usage sources:
|
|
249
|
+
|
|
250
|
+
- body family match: 40%;
|
|
251
|
+
- heading usage-share alignment: 35%;
|
|
252
|
+
- whole-page element-share alignment: 25%.
|
|
253
|
+
|
|
254
|
+
Usage-share differences use exponential similarity with sensitivity `2`. Heading hierarchy compares every observed size with its closest reference size by relative error, then uses sensitivity `4`.
|
|
255
|
+
|
|
256
|
+
```text
|
|
257
|
+
raw typography =
|
|
258
|
+
declared usage × 55%
|
|
259
|
+
+ heading hierarchy × 20%
|
|
260
|
+
+ actual loaded face × 25%
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
When hierarchy is unavailable, the formula re-normalizes over the remaining `80%`. If no acceptable face is actually present, the score is capped at `35%` before style grammar is applied.
|
|
264
|
+
|
|
265
|
+
Role-specific style grammar compares exact normalized size, weight, resolved line height, and letter spacing. Family grammar compares the leading family per heading role, including only byte-verified aliases. Both grammars are supporting penalties with 20% weight each.
|
|
266
|
+
|
|
267
|
+
The final typography score is capped at `74` when:
|
|
268
|
+
|
|
269
|
+
- style grammar is exactly zero; or
|
|
270
|
+
- the weakest reference family role scores below `0.5`.
|
|
271
|
+
|
|
272
|
+
The `75` load-bearing gate therefore cannot pass through family declarations alone.
|
|
273
|
+
|
|
274
|
+
## 5. Asset verifier
|
|
275
|
+
|
|
276
|
+
Source: [`verify-assets.ts`](../packages/core/src/verify-assets.ts)
|
|
277
|
+
|
|
278
|
+
### Evidence classes and internal weights
|
|
279
|
+
|
|
280
|
+
| Asset class | Asset-check weight |
|
|
281
|
+
| --- | ---: |
|
|
282
|
+
| Logo | 35% |
|
|
283
|
+
| Fonts | 20% |
|
|
284
|
+
| Images | 20% |
|
|
285
|
+
| Icons and inline SVGs | 10% |
|
|
286
|
+
| Videos | 7.5% |
|
|
287
|
+
| Textures/backgrounds | 7.5% |
|
|
288
|
+
|
|
289
|
+
Only classes present in the viewport-scoped reference are included; their weights are re-normalized. Assets with the same fingerprint are counted once.
|
|
290
|
+
|
|
291
|
+
A target logo candidate must match an expected source, carry a brand-relevant label, or match expected bytes. Unrelated root-link or technology icons do not count merely because they appear near the header.
|
|
292
|
+
|
|
293
|
+
### Presence and identity
|
|
294
|
+
|
|
295
|
+
Presence compares expected and observed counts logarithmically. When the reference has fingerprints:
|
|
296
|
+
|
|
297
|
+
- identity precision is the share of observed fingerprint rows matching reference bytes;
|
|
298
|
+
- identity coverage is the share of reference fingerprint rows found in the target;
|
|
299
|
+
- identity score is `min(precision, coverage)`;
|
|
300
|
+
- identity confidence is `(1 + identity score) / 2` when any identity matches, otherwise zero;
|
|
301
|
+
- class score is `presence × identity confidence`.
|
|
302
|
+
|
|
303
|
+
If the reference is fingerprinted but the target exposes no fingerprints, the class receives only `presence × 0.35`.
|
|
304
|
+
|
|
305
|
+
The measured class scores are combined by weight. A weak class creates a balance penalty when identity is explicitly wrong, identity coverage is below `35%`, or presence is below `50%`. Any measured identity mismatch or identity coverage below `35%` caps the complete asset score at `60`.
|
|
306
|
+
|
|
307
|
+
The report retains precision, coverage, matched counts, the weak classes, identity ceilings, and representative reference assets.
|
|
308
|
+
|
|
309
|
+
## 6. Motion verifier
|
|
310
|
+
|
|
311
|
+
Source: [`verify-motion.ts`](../packages/core/src/verify-motion.ts)
|
|
312
|
+
|
|
313
|
+
### Evidence
|
|
314
|
+
|
|
315
|
+
- counted animations, transitions, and keyframes;
|
|
316
|
+
- duration tokens converted to milliseconds;
|
|
317
|
+
- easing functions;
|
|
318
|
+
- animated properties;
|
|
319
|
+
- delay, iteration, direction, and fill cadence annotations;
|
|
320
|
+
- normalized keyframe trajectories;
|
|
321
|
+
- CSS `prefers-reduced-motion` support when extraction measured it.
|
|
322
|
+
|
|
323
|
+
If a viewport-scoped reference explicitly contains no motion, a static target scores `100` and any target motion scores `0`. Without viewport-scoped evidence, an empty motion inventory is `not-measured` rather than assumed static.
|
|
324
|
+
|
|
325
|
+
### Score
|
|
326
|
+
|
|
327
|
+
```text
|
|
328
|
+
count score = animation count similarity × 40%
|
|
329
|
+
+ transition count similarity × 50%
|
|
330
|
+
+ keyframe count similarity × 10%
|
|
331
|
+
|
|
332
|
+
grammar = count score × 30%
|
|
333
|
+
+ duration grammar × 25%
|
|
334
|
+
+ easing-set similarity × 20%
|
|
335
|
+
+ animated-property similarity × 25%
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Count similarity uses the ratio of `log1p` counts. Duration grammar is the minimum of target precision and reference coverage; individual duration similarity is based on `abs(log(target/reference))` with sensitivity `1.3`.
|
|
339
|
+
|
|
340
|
+
Cadence, measured reduced-motion alignment, and keyframe trajectory each apply a supporting penalty with 20% weight. `from`/`to` keyframe stops normalize to `0%`/`100%`, and animation names and formatting are removed before trajectory comparison.
|
|
341
|
+
|
|
342
|
+
If duration grammar is below `0.6`, the entire motion score is capped at `60`.
|
|
343
|
+
|
|
344
|
+
## 7. Shape verifier
|
|
345
|
+
|
|
346
|
+
Source: [`verify-shape.ts`](../packages/core/src/verify-shape.ts)
|
|
347
|
+
|
|
348
|
+
### Evidence
|
|
349
|
+
|
|
350
|
+
- viewport-scoped radii;
|
|
351
|
+
- counted box and text shadows;
|
|
352
|
+
- counted border grammar.
|
|
353
|
+
|
|
354
|
+
Radius parsing preserves percentage radii, multi-corner geometry, and browser-normalized pill radii. CSS Color 4 values inside shadows and borders are normalized to comparable colors; fully transparent shadow layers are ignored.
|
|
355
|
+
|
|
356
|
+
### Score
|
|
357
|
+
|
|
358
|
+
Radius precision occurrence-weights each target radius against its closest reference. Radius coverage scores each expected radius against its closest target. The radius score is the lower of precision and coverage.
|
|
359
|
+
|
|
360
|
+
Shadow similarity combines:
|
|
361
|
+
|
|
362
|
+
- distinct value coverage: 60%;
|
|
363
|
+
- occurrence/usage overlap: 40%.
|
|
364
|
+
|
|
365
|
+
When radii and shadows both exist, they contribute `60%` and `40%`; otherwise the available family carries the rendered shape score. Border grammar then applies a 20% supporting penalty.
|
|
366
|
+
|
|
367
|
+
If viewport-scoped evidence explicitly describes a flat system, a target with no visible radius, shadow, or border treatments scores `100`; adding any of those treatments scores `0`. Without scoped evidence, an empty shape inventory is `not-measured`.
|
|
368
|
+
|
|
369
|
+
## 8. Voice verifier
|
|
370
|
+
|
|
371
|
+
Source: [`verify-voice.ts`](../packages/core/src/verify-voice.ts)
|
|
372
|
+
|
|
373
|
+
Voice is intentionally heuristic and always returns `partial` when reference copy exists.
|
|
374
|
+
|
|
375
|
+
### Evidence
|
|
376
|
+
|
|
377
|
+
- reference and target headings;
|
|
378
|
+
- reference and target calls to action;
|
|
379
|
+
- exclamation rate;
|
|
380
|
+
- median heading word count;
|
|
381
|
+
- a fixed hype-language vocabulary;
|
|
382
|
+
- CTA terminology overlap after stop-word removal.
|
|
383
|
+
|
|
384
|
+
### Score
|
|
385
|
+
|
|
386
|
+
```text
|
|
387
|
+
heuristic =
|
|
388
|
+
exclamation-rate alignment × 35%
|
|
389
|
+
+ median heading-length alignment × 35%
|
|
390
|
+
+ hype-rate alignment × 30%
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The component penalties are linear:
|
|
394
|
+
|
|
395
|
+
- exclamation difference: `250` points per unit difference;
|
|
396
|
+
- median heading difference: `7` points per word;
|
|
397
|
+
- hype-rate difference: `220` points per unit difference.
|
|
398
|
+
|
|
399
|
+
When the heuristic score is at least `85`, CTA term Jaccard similarity can reduce it through a 35% supporting penalty. Copy sample count similarity creates a confidence ceiling of `60 + sample coverage × 40`.
|
|
400
|
+
|
|
401
|
+
No reference copy produces `not-measured`. Reference copy with no target copy produces a partial score of `0`.
|
|
402
|
+
|
|
403
|
+
Voice has no load-bearing gate and the standard report caveat explicitly requires human review.
|
|
404
|
+
|
|
405
|
+
## Image-only verification
|
|
406
|
+
|
|
407
|
+
Passing a PNG or other supported raster file does not run the eight rendered-page verifiers. Raffina captures only a raster palette and deliberately returns a guarded compatibility report:
|
|
408
|
+
|
|
409
|
+
- score `40`;
|
|
410
|
+
- verdict `off-brand`;
|
|
411
|
+
- coverage `0.25`;
|
|
412
|
+
- partial palette evidence;
|
|
413
|
+
- typography, shape, and voice marked unavailable;
|
|
414
|
+
- a repair asking for a rendered HTML target.
|
|
415
|
+
|
|
416
|
+
Image-only mode must not be used as proof that an implementation is on-brand.
|
|
417
|
+
|
|
418
|
+
## Repair action ordering
|
|
419
|
+
|
|
420
|
+
For checks below `85`, Raffina ranks at most five actions:
|
|
421
|
+
|
|
422
|
+
1. categories failing an applicable load-bearing gate;
|
|
423
|
+
2. weighted repair impact: `(85 - category score) × category weight`;
|
|
424
|
+
3. lower score as the tie-breaker.
|
|
425
|
+
|
|
426
|
+
Category repair functions use the measurement details rather than emitting only generic advice. Examples include:
|
|
427
|
+
|
|
428
|
+
- exact worst screenshot grid regions and target→reference measurements;
|
|
429
|
+
- missing or misassigned layout regions and closest target candidates;
|
|
430
|
+
- target→reference content widths and component properties;
|
|
431
|
+
- the loaded font face or role that failed identity/style grammar;
|
|
432
|
+
- mismatched fingerprinted asset classes and representatives;
|
|
433
|
+
- target→reference durations, easings, animated properties, and cadence;
|
|
434
|
+
- unmatched shadow, border, radius, palette, and CTA samples.
|
|
435
|
+
|
|
436
|
+
When an iteration reaches its requested numerical target but still has no required repair and is not fully polished, Raffina ranks polish actions by `(100 - category score) × category weight`.
|
|
437
|
+
|
|
438
|
+
## Iteration semantics
|
|
439
|
+
|
|
440
|
+
`raffina iterate` stores the report's category scores, score delta, category deltas, regressions, evidence coverage, verdict, next actions, and optional screenshot/report paths.
|
|
441
|
+
|
|
442
|
+
- A category regression is recorded when its delta is less than `-2` points.
|
|
443
|
+
- A target is met only when the score reaches `--target-score` **and** the report verdict is `on-brand`.
|
|
444
|
+
- Histories cannot mix brand, target, viewport, profile, target score, or a different extracted observation.
|
|
445
|
+
- Best runs rank complete `on-brand` results above evidence-rich `needs-review`, evidence-rich `off-brand`, low-coverage `needs-review`, and low-coverage `off-brand`; score and then coverage break ties.
|
|
446
|
+
|
|
447
|
+
## Report shape
|
|
448
|
+
|
|
449
|
+
A full report contains:
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
{
|
|
453
|
+
schemaVersion: "1.0.0";
|
|
454
|
+
profile?: "brand" | "visual-system";
|
|
455
|
+
target: string;
|
|
456
|
+
brand: string;
|
|
457
|
+
verifiedAt: string;
|
|
458
|
+
score: number;
|
|
459
|
+
verdict: "on-brand" | "needs-review" | "off-brand";
|
|
460
|
+
coverage: number;
|
|
461
|
+
checks: Record<string, CheckResult>;
|
|
462
|
+
findings: Array<{
|
|
463
|
+
category: string;
|
|
464
|
+
severity: "pass" | "warning" | "fail";
|
|
465
|
+
message: string;
|
|
466
|
+
}>;
|
|
467
|
+
nextActions: Array<{
|
|
468
|
+
category: string;
|
|
469
|
+
priority: 1 | 2 | 3 | 4 | 5;
|
|
470
|
+
instruction: string;
|
|
471
|
+
evidence: string;
|
|
472
|
+
}>;
|
|
473
|
+
caveats: string[];
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
Use `raffina verify ... --json` for the complete report. `raffina iterate ... --json --summary` returns only the current run and bounded history metadata while the full typed history and optional verification report remain on disk.
|
|
478
|
+
|
|
479
|
+
## How the verifier is tested
|
|
480
|
+
|
|
481
|
+
The deterministic suites use frozen observations rather than changing live websites:
|
|
482
|
+
|
|
483
|
+
- [`verify-foundation.test.ts`](../packages/core/test/verify-foundation.test.ts) covers profiles, viewport selection, missing evidence, screenshot integrity, determinism, and color evidence;
|
|
484
|
+
- [`verify-composition.test.ts`](../packages/core/test/verify-composition.test.ts) covers density, content width, global region assignment, spacing, borders, component roles, and interrupted captures;
|
|
485
|
+
- [`verify-typography.test.ts`](../packages/core/test/verify-typography.test.ts) covers loaded font bytes, aliases, usage, hierarchy, fallbacks, and secondary families;
|
|
486
|
+
- [`verify-assets-shape.test.ts`](../packages/core/test/verify-assets-shape.test.ts) covers logos, responsive assets, identity coverage, icons, shadows, radii, and textures;
|
|
487
|
+
- [`verify-motion-voice.test.ts`](../packages/core/test/verify-motion-voice.test.ts) covers timing, easing, properties, trajectories, reduced motion, copy coverage, tone, and repair ranking.
|
|
488
|
+
|
|
489
|
+
The frozen Cursor, adversarial depth, and Expo benchmarks assert separation between genuine source captures and hard negatives. They are release gates, not examples tuned at runtime.
|
|
490
|
+
|
|
491
|
+
## Known boundaries
|
|
492
|
+
|
|
493
|
+
- Screenshot and composition verification is specific to the captured viewport and state.
|
|
494
|
+
- Interaction sequences, routes, dark mode, and modal states must be extracted separately.
|
|
495
|
+
- Fingerprints require both sides to expose fetchable bytes; otherwise asset evidence is weaker.
|
|
496
|
+
- Motion compares CSS grammar and normalized trajectories, not recorded animation frames.
|
|
497
|
+
- Voice remains a bounded heuristic.
|
|
498
|
+
- Public-web evidence cannot supply private brand rules, asset licenses, trademark permission, accessibility approval, or brand-owner intent.
|
|
499
|
+
|
|
500
|
+
An `on-brand` verdict means the measured target passed Raffina's evidence and gate model for that capture. It does not replace design, legal, accessibility, or product review.
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "raffina",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Refine AI-generated software with evidence-backed design extraction and verification",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"raffina": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"docs",
|
|
13
|
+
"skills"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"bun": ">=1.3.0"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"ai",
|
|
20
|
+
"brand",
|
|
21
|
+
"design-system",
|
|
22
|
+
"visual-regression",
|
|
23
|
+
"verification",
|
|
24
|
+
"agents",
|
|
25
|
+
"cli",
|
|
26
|
+
"bun"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/zvadaadam/raffina.git"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/zvadaadam/raffina#readme",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/zvadaadam/raffina/issues"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: raffina-design-loop
|
|
3
|
+
description: Run evidence-backed design reproduction and brand-adherence loops with the Raffina CLI. Use when an agent needs to extract a visual system from a URL, inspect responsive brand evidence, implement that system in a web or mobile-app harness, verify rendered output, diagnose an off-brand result, or iterate across viewports until both the score and load-bearing gates pass.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Raffina Design Loop
|
|
7
|
+
|
|
8
|
+
Use Raffina as the measurement engine in an agent-led implementation loop. Preserve the target app's behavior and architecture while making its rendered design follow the extracted source of truth.
|
|
9
|
+
|
|
10
|
+
Read [references/cli-contract.md](references/cli-contract.md) before running commands or interpreting JSON.
|
|
11
|
+
|
|
12
|
+
## 1. Define the evaluation boundary
|
|
13
|
+
|
|
14
|
+
Confirm these inputs from the request or infer conservative defaults:
|
|
15
|
+
|
|
16
|
+
- Reference URL and target app path or URL.
|
|
17
|
+
- Required viewports. Default to desktop and mobile when the source is responsive.
|
|
18
|
+
- Identity policy: exact reproduction/adherence, or visual-system transfer that preserves the target product's name, logo, and copy.
|
|
19
|
+
- Whether edits are authorized. Verification alone does not authorize target changes.
|
|
20
|
+
- Target score. Default to `85`, but never use score alone as completion.
|
|
21
|
+
- Target start command and stable local URL for rendered verification.
|
|
22
|
+
|
|
23
|
+
Keep generated evidence under `.raffina/` in the target project unless the user requests another location. Use distinct iteration histories per viewport.
|
|
24
|
+
|
|
25
|
+
Use `--profile brand` for exact reproduction/adherence. Use `--profile visual-system` for transfer: source asset identity and voice remain diagnostic checks but are excluded from score, verdict, and action ordering. Never describe a visual-system `on-brand` verdict as certification of source logo, asset, copy, or voice adherence.
|
|
26
|
+
|
|
27
|
+
For native mobile apps, verify a deterministic browser-visible harness such as Expo web, Storybook, or a component gallery at the intended device viewport. State that this measures visual brand adherence, not native gestures, platform rendering, or device behavior.
|
|
28
|
+
|
|
29
|
+
## 2. Extract the reference
|
|
30
|
+
|
|
31
|
+
Prefer the local rendered-browser provider. Use Firecrawl or hybrid acquisition only when the user authorized it and a key is already available in the environment. Never print, persist, or copy a secret into commands, reports, or source files.
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
raffina extract <reference-url> \
|
|
35
|
+
--provider local \
|
|
36
|
+
--viewport 1440x1000 \
|
|
37
|
+
--viewport 390x844 \
|
|
38
|
+
--output .raffina/reference.brand.json \
|
|
39
|
+
--screenshot '.raffina/reference-{viewport}.png' \
|
|
40
|
+
--json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Inspect the artifact before implementing:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
raffina inspect .raffina/reference.brand.json --json --summary
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Inspect every saved reference PNG with an available image viewer before choosing assets or composition. Evaluate evidence quality, not just command success. Check coverage and the extracted color scheme, CSS variables, fonts, logos/assets, spacing, content widths, shape, shadows, motion, voice, component styles, layout regions, and responsive captures. Use layout regions to reconstruct large panels and overlays from their selector, bounds, positioning, z-index, background, border, radius, shadow, and overflow. Use asset roles with their viewport, rendered dimensions, and position to distinguish prominent, navigation, card, avatar, and ordinary content media; confirm both surfaces against the reference PNG. Request focused fields from the full inspection only after choosing a category to implement.
|
|
50
|
+
|
|
51
|
+
If a load-bearing category is absent or `not-measured`, re-extract with an appropriate viewport/provider or report the limitation. Do not fabricate a design rule from missing evidence.
|
|
52
|
+
|
|
53
|
+
## 3. Translate evidence into an implementation plan
|
|
54
|
+
|
|
55
|
+
Inspect the target codebase before editing. Find its theme/token entry points, global styles, layout primitives, reusable components, asset pipeline, route structure, and development command.
|
|
56
|
+
|
|
57
|
+
Prioritize changes in this order:
|
|
58
|
+
|
|
59
|
+
1. Page composition, content widths, responsive structure, and whitespace rhythm.
|
|
60
|
+
2. Real fonts, logos, illustrations, icons, and other distinctive assets.
|
|
61
|
+
3. Theme polarity, palette, CSS variables, typography hierarchy, and shape language.
|
|
62
|
+
4. Component styles, borders, shadows, textures, interaction states, and motion.
|
|
63
|
+
5. Voice and representative content density.
|
|
64
|
+
|
|
65
|
+
Treat layout maps as composition signals rather than literal CSS grids. Preserve functional behavior. Do not imitate copyrighted copy or proprietary functionality when structure and representative content are sufficient.
|
|
66
|
+
|
|
67
|
+
Use `apply` as a planning aid where useful. It is a dry run unless `--write` is supplied:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
raffina apply .raffina/reference.brand.json <target-path> --json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Review its plan before using `--write`. Manual code changes are normally still required; a generated token layer is not a complete reproduction.
|
|
74
|
+
|
|
75
|
+
## 4. Record a rendered baseline
|
|
76
|
+
|
|
77
|
+
Start the target app and wait for its stable local URL. Verify the actual rendered page, not source files alone.
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
raffina iterate <target-url> \
|
|
81
|
+
--brand .raffina/reference.brand.json \
|
|
82
|
+
--profile <brand|visual-system> \
|
|
83
|
+
--history .raffina/iterations-1440x1000.json \
|
|
84
|
+
--viewport 1440x1000 \
|
|
85
|
+
--target-score 85 \
|
|
86
|
+
--label baseline \
|
|
87
|
+
--screenshot .raffina/baseline-1440x1000.png \
|
|
88
|
+
--report .raffina/baseline-1440x1000.report.json \
|
|
89
|
+
--json --summary
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Read the per-check scores, verdict, coverage, regressions, and ordered `nextActions`. A high overall score can hide a failed content-width, typography, layout, asset, or motion gate.
|
|
93
|
+
|
|
94
|
+
After choosing a category, read its full details from the run's `--report` artifact. It comes from the same stabilized observation as the compact history and screenshot, so do not re-measure merely to recover deep evidence.
|
|
95
|
+
|
|
96
|
+
When composition reports `layoutRegionGrammar`, treat unmatched layered-region guidance as a structural hypothesis before component polish. Reshape the named `closestObserved` target candidate, or add/split a region when that candidate is already assigned elsewhere; then match bounds, stacking, clipping, radius, and elevation and inspect the measured PNG. Reference selectors identify evidence but do not need to be copied into the target.
|
|
97
|
+
|
|
98
|
+
## 5. Iterate one hypothesis at a time
|
|
99
|
+
|
|
100
|
+
Treat ordered `nextActions` as the default shortlist, not as a substitute for the exact reference and target frames. Choose the highest-priority action that is supported by the focused check details and relevant to the declared identity policy, viewport, visible state, and requested outcome. If whole-page inventory or exact byte identity ranks above a larger visible structural mismatch, keep that debt unresolved but select the screenshot-grounded hypothesis; never erase the debt or claim completion.
|
|
101
|
+
|
|
102
|
+
Before editing, state one hypothesis with its active category, measured evidence, expected check/gate improvement, preservation constraints, and visual success condition. Make only that coherent change, then record another run with a specific label:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
raffina iterate <target-url> \
|
|
106
|
+
--brand .raffina/reference.brand.json \
|
|
107
|
+
--profile <brand|visual-system> \
|
|
108
|
+
--history .raffina/iterations-1440x1000.json \
|
|
109
|
+
--viewport 1440x1000 \
|
|
110
|
+
--target-score 85 \
|
|
111
|
+
--label tighten-content-width \
|
|
112
|
+
--screenshot .raffina/tighten-content-width-1440x1000.png \
|
|
113
|
+
--report .raffina/tighten-content-width-1440x1000.report.json \
|
|
114
|
+
--json --summary
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Keep a change when it improves the intended check without an unacceptable regression elsewhere. If the score drops or `regressions` identifies a meaningful loss, diagnose the category and revise the change. Do not destructively reset unrelated user work.
|
|
118
|
+
|
|
119
|
+
`missingLayoutRegions` means the optimal one-to-one assignment did not give that reference region a target profile above the match threshold; it does not prove the target lacks a visible panel. Each entry may include `closestObserved` geometry, similarity, and its current reference assignment; `layoutRegionAssignments` shows the complete selected pairing. Use both before adding markup so the loop reshapes an available candidate or deliberately splits one instead of duplicating it accidentally.
|
|
120
|
+
|
|
121
|
+
Repeat separately for every required viewport. Never reuse one viewport's history for another viewport. Re-extract when the reference changed or when iteration reveals that the stored evidence is insufficient.
|
|
122
|
+
|
|
123
|
+
Use `--screenshot` to save the exact stabilized PNG measured by each milestone run, then inspect it with an available image viewer or browser-control facility. If no visual inspection facility is available, preserve the PNG and state that final human review is pending. The verifier measures observable rules and similarity signals; it does not replace design judgment, content review, accessibility testing, or functional testing.
|
|
124
|
+
|
|
125
|
+
## 6. Apply strict completion gates
|
|
126
|
+
|
|
127
|
+
Declare the visual loop complete only when every required viewport has:
|
|
128
|
+
|
|
129
|
+
- `targetMet: true`.
|
|
130
|
+
- `verdict: on-brand` under the declared profile.
|
|
131
|
+
- Score at or above the requested target.
|
|
132
|
+
- Acceptable coverage with no critical category silently unmeasured.
|
|
133
|
+
- No unresolved load-bearing `nextActions` or material regressions.
|
|
134
|
+
- A final human screenshot review that finds no obvious structural mismatch.
|
|
135
|
+
|
|
136
|
+
If the score target is reached while the verdict is not `on-brand`, continue. Report limitations precisely when a category cannot be measured.
|
|
137
|
+
|
|
138
|
+
Finish by reporting the reference artifact, viewport histories, before/after scores, meaningful check deltas, remaining caveats, and the exact validation commands used.
|