@ggui-ai/ui-gen 0.23.0 → 0.25.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/dist/adapter-Cg_rkpGq.d.ts +201 -0
- package/dist/adapters/generation-dispatch.d.ts +58 -15
- package/dist/adapters/generation-dispatch.js +1418 -180
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +3 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +12 -8
- package/dist/advanced/index.js +1467 -239
- package/dist/advanced/index.js.map +1 -1
- package/dist/{axes-CzLEMDeB.d.ts → axes-CKVCzR_x.d.ts} +1 -1
- package/dist/bank-CT4eEkta.d.ts +118 -0
- package/dist/blueprint-validator.js +153 -59
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +24 -12
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +12 -4
- package/dist/check/index.js +94 -2
- package/dist/check/index.js.map +1 -1
- package/dist/classifier/index.d.ts +2 -2
- package/dist/compose.d.ts +2 -2
- package/dist/{create-ui-generator-D0kbWwln.d.ts → create-ui-generator-DFnnArrv.d.ts} +43 -11
- package/dist/{dispatch-wxaIMwZt.d.ts → dispatch-CStl9eAw.d.ts} +5 -3
- package/dist/evaluation/axis-checks/index.d.ts +6 -5
- package/dist/evaluation/axis-checks/index.js +90 -2
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +4 -3
- package/dist/evaluation/axis-checks/registry.js +88 -1
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +301 -23
- package/dist/evaluation/index.js +746 -30
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +9 -1
- package/dist/evaluation/types.js.map +1 -1
- package/dist/fragments/index.d.ts +3 -3
- package/dist/harness/check/runtime-render/index.d.ts +30 -11
- package/dist/harness/check/runtime-render/index.js +203 -60
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.d.ts +8 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +72 -32
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +5 -4
- package/dist/harness/index.js +33 -15
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +4 -3
- package/dist/harness/runtime.d.ts +11 -4
- package/dist/harness/runtime.js +26 -14
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +13 -4
- package/dist/harness/types-public.js +33 -15
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +4 -3
- package/dist/hash.d.ts +1 -1
- package/dist/index.d.ts +59 -8
- package/dist/index.js +1476 -241
- package/dist/index.js.map +1 -1
- package/dist/{llm-router-DwwUK1bz.d.ts → llm-router-nfO7wPec.d.ts} +3 -1
- package/dist/patch.d.ts +4 -3
- package/dist/policy.d.ts +8 -5
- package/dist/policy.js.map +1 -1
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +1 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/tools/render-check-worker.d.ts +7 -1
- package/dist/tools/render-check-worker.js +55 -23
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-BOvHNG7K.d.ts → types-Dh1s4K9j.d.ts} +1 -1
- package/dist/{types-public-Ow-tNtFE.d.ts → types-public-BJTLpzWn.d.ts} +208 -15
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/workflows.d.ts +4 -3
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +173 -98
- package/src/adapters/tools.ts +2 -1
- package/src/boilerplate/free-design-prompt.ts +1 -1
- package/src/boilerplate/generate.ts +26 -1
- package/src/boilerplate/hard-sections.ts +13 -13
- package/src/boilerplate/system-prompt.ts +1 -1
- package/src/check/index.ts +1 -0
- package/src/check/run-tier0.ts +80 -1
- package/src/check/sample-props.ts +63 -0
- package/src/coding-agent/tools.ts +4 -1
- package/src/create-harness.ts +3 -3
- package/src/create-ui-generator.ts +204 -95
- package/src/evaluation/__fixtures__/png.ts +100 -0
- package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
- package/src/evaluation/axis-checks/checks/universal.ts +111 -0
- package/src/evaluation/axis-checks/dispatch.ts +4 -1
- package/src/evaluation/criteria/bank.ts +85 -0
- package/src/evaluation/criteria/context.ts +52 -0
- package/src/evaluation/criteria/resolve.ts +159 -0
- package/src/evaluation/criteria/select.ts +79 -0
- package/src/evaluation/index.ts +23 -0
- package/src/evaluation/ink-extent.ts +200 -0
- package/src/evaluation/theme-css.ts +12 -5
- package/src/evaluation/types-public.ts +200 -11
- package/src/evaluation/types.ts +9 -0
- package/src/evaluation/visual-evaluator.ts +636 -30
- package/src/harness/check/runtime-render/adapter.ts +221 -75
- package/src/harness/check/runtime-render/index.ts +75 -2
- package/src/harness/check/runtime-render/render-check-host.ts +82 -19
- package/src/harness/check/runtime-render/render-check-worker.ts +83 -29
- package/src/harness/check/runtime-render/render-check.ts +154 -117
- package/src/harness/coding/generate-task-runner.ts +60 -4
- package/src/harness/coding/init-session.ts +13 -0
- package/src/harness/coding/run-coding-turn.ts +7 -3
- package/src/harness/coding/run-eval-round.ts +257 -15
- package/src/harness/policy.ts +11 -0
- package/src/harness/primitive-slice.ts +104 -0
- package/src/harness/runtime.ts +5 -0
- package/src/harness/types-public.ts +9 -1
- package/src/harness-tool-names.ts +38 -0
- package/src/index.ts +16 -0
- package/src/policy.ts +7 -4
- package/src/run-check.ts +2 -0
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/get-wire.ts +1 -1
- package/src/tools/render-check-worker.ts +74 -23
- package/src/tools/render-check.ts +0 -60
- package/src/tools.ts +11 -1
- package/src/validation/primitives.ts +1 -1
- package/dist/render-check-Blwj8gB6.d.ts +0 -105
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ink extent of a judge screenshot (ggui#1120): how far down the frame anything was painted, read
|
|
3
|
+
* from the pixels the judge already holds. `contentHeight` is the document's scroll height, floored at
|
|
4
|
+
* the viewport by definition — it can see overflow and never under-fill, and never a blank.
|
|
5
|
+
*
|
|
6
|
+
* The reading: the region's DOMINANT colour (its most common pixel) is the ground; a pixel is ink when
|
|
7
|
+
* any channel differs from it by more than {@link INK_TOLERANCE}; `lastInkRow` is the last region row
|
|
8
|
+
* with ink and `ratio` is `(lastInkRow + 1) / region.height`. Dominant, not the corners: an opening
|
|
9
|
+
* hero band makes the top corners content, and a host panel's rounded corners expose the scrim behind
|
|
10
|
+
* it (measured on the ggui#1083 judge frames — corner sampling read a card that fills as 1.0 and a
|
|
11
|
+
* panelled page as all ink).
|
|
12
|
+
*
|
|
13
|
+
* Only the ZERO is judged (`canvas-blank`, the judge's verdict): a region with no ink is one flat
|
|
14
|
+
* colour — the tree mounted and painted nothing, the mount-path class no compile-time check can reach
|
|
15
|
+
* (ggui#1104 / #1119). Every other value is reported, never scored: the corpus does not support a
|
|
16
|
+
* dead-space threshold (#1120).
|
|
17
|
+
*
|
|
18
|
+
* A PNG reader of exactly the judge's own captures — 8-bit RGB / RGBA, non-interlaced, `node:zlib`
|
|
19
|
+
* for the IDAT stream and the five scanline filters by hand — so a published package takes no image
|
|
20
|
+
* dependency. Any other PNG reads as unmeasurable (a reason), never as blank.
|
|
21
|
+
*/
|
|
22
|
+
import { inflateSync } from 'node:zlib';
|
|
23
|
+
|
|
24
|
+
/** A channel difference at or below this is the same colour (anti-aliasing and compositing noise). */
|
|
25
|
+
export const INK_TOLERANCE = 8;
|
|
26
|
+
|
|
27
|
+
export interface PixelRegion {
|
|
28
|
+
readonly x: number;
|
|
29
|
+
readonly y: number;
|
|
30
|
+
readonly width: number;
|
|
31
|
+
readonly height: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface InkExtent {
|
|
35
|
+
/** `(lastInkRow + 1) / region.height`, rounded to 3 places; `0` when nothing differs from the ground. */
|
|
36
|
+
readonly ratio: number;
|
|
37
|
+
/** The last region row (0-based from the region's top) carrying ink; `null` when there is none — the blank. */
|
|
38
|
+
readonly lastInkRow: number | null;
|
|
39
|
+
/** The region read, in image px. */
|
|
40
|
+
readonly region: PixelRegion;
|
|
41
|
+
/** The dominant colour the ink was read against, `[r, g, b]`. */
|
|
42
|
+
readonly ground: readonly [number, number, number];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Why a capture could not be read — reported, never mistaken for a blank. */
|
|
46
|
+
export interface InkUnreadable {
|
|
47
|
+
readonly reason: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DecodedImage {
|
|
51
|
+
readonly width: number;
|
|
52
|
+
readonly height: number;
|
|
53
|
+
readonly channels: 3 | 4;
|
|
54
|
+
/** Row-major, `channels` bytes per pixel, scanline filters undone. */
|
|
55
|
+
readonly pixels: Uint8Array;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] as const;
|
|
59
|
+
|
|
60
|
+
/** The PNG subset the judge's browser writes: 8-bit RGB (colour type 2) or RGBA (6), not interlaced. */
|
|
61
|
+
export function decodePng(png: Uint8Array): DecodedImage | InkUnreadable {
|
|
62
|
+
const buf = Buffer.from(png.buffer, png.byteOffset, png.byteLength);
|
|
63
|
+
if (buf.length < 8 + 25) return { reason: `not a PNG: ${buf.length} bytes` };
|
|
64
|
+
for (let i = 0; i < PNG_SIGNATURE.length; i++) {
|
|
65
|
+
if (buf[i] !== PNG_SIGNATURE[i]) return { reason: 'not a PNG: signature mismatch' };
|
|
66
|
+
}
|
|
67
|
+
let width = 0;
|
|
68
|
+
let height = 0;
|
|
69
|
+
let channels: 3 | 4 | null = null;
|
|
70
|
+
const idat: Buffer[] = [];
|
|
71
|
+
let offset = 8;
|
|
72
|
+
while (offset + 8 <= buf.length) {
|
|
73
|
+
const length = buf.readUInt32BE(offset);
|
|
74
|
+
const type = buf.toString('latin1', offset + 4, offset + 8);
|
|
75
|
+
const start = offset + 8;
|
|
76
|
+
if (start + length + 4 > buf.length) return { reason: `truncated PNG: ${type} chunk runs past the end` };
|
|
77
|
+
const data = buf.subarray(start, start + length);
|
|
78
|
+
if (type === 'IHDR') {
|
|
79
|
+
if (length !== 13) return { reason: `malformed IHDR: ${length} bytes` };
|
|
80
|
+
width = data.readUInt32BE(0);
|
|
81
|
+
height = data.readUInt32BE(4);
|
|
82
|
+
const bitDepth = data[8];
|
|
83
|
+
const colorType = data[9];
|
|
84
|
+
const interlace = data[12];
|
|
85
|
+
if (bitDepth !== 8) return { reason: `unsupported PNG: bit depth ${bitDepth}` };
|
|
86
|
+
if (interlace !== 0) return { reason: 'unsupported PNG: interlaced' };
|
|
87
|
+
if (colorType === 2) channels = 3;
|
|
88
|
+
else if (colorType === 6) channels = 4;
|
|
89
|
+
else return { reason: `unsupported PNG: colour type ${colorType}` };
|
|
90
|
+
} else if (type === 'IDAT') {
|
|
91
|
+
idat.push(data);
|
|
92
|
+
} else if (type === 'IEND') {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
offset = start + length + 4;
|
|
96
|
+
}
|
|
97
|
+
if (channels === null) return { reason: 'malformed PNG: no IHDR' };
|
|
98
|
+
if (width === 0 || height === 0) return { reason: `empty PNG: ${width}×${height}` };
|
|
99
|
+
if (idat.length === 0) return { reason: 'malformed PNG: no IDAT' };
|
|
100
|
+
let raw: Buffer;
|
|
101
|
+
try {
|
|
102
|
+
raw = inflateSync(Buffer.concat(idat));
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return { reason: `corrupt IDAT stream: ${error instanceof Error ? error.message : String(error)}` };
|
|
105
|
+
}
|
|
106
|
+
const stride = width * channels;
|
|
107
|
+
if (raw.length !== (stride + 1) * height) {
|
|
108
|
+
return { reason: `malformed PNG: ${raw.length} raw bytes for ${width}×${height}×${channels}` };
|
|
109
|
+
}
|
|
110
|
+
const pixels = new Uint8Array(stride * height);
|
|
111
|
+
const bpp = channels;
|
|
112
|
+
for (let y = 0; y < height; y++) {
|
|
113
|
+
const filter = raw[y * (stride + 1)]!;
|
|
114
|
+
const inRow = y * (stride + 1) + 1;
|
|
115
|
+
const outRow = y * stride;
|
|
116
|
+
const prevRow = outRow - stride;
|
|
117
|
+
for (let i = 0; i < stride; i++) {
|
|
118
|
+
const x = raw[inRow + i]!;
|
|
119
|
+
const a = i >= bpp ? pixels[outRow + i - bpp]! : 0;
|
|
120
|
+
const b = y > 0 ? pixels[prevRow + i]! : 0;
|
|
121
|
+
const c = y > 0 && i >= bpp ? pixels[prevRow + i - bpp]! : 0;
|
|
122
|
+
let value: number;
|
|
123
|
+
switch (filter) {
|
|
124
|
+
case 0:
|
|
125
|
+
value = x;
|
|
126
|
+
break;
|
|
127
|
+
case 1:
|
|
128
|
+
value = x + a;
|
|
129
|
+
break;
|
|
130
|
+
case 2:
|
|
131
|
+
value = x + b;
|
|
132
|
+
break;
|
|
133
|
+
case 3:
|
|
134
|
+
value = x + ((a + b) >> 1);
|
|
135
|
+
break;
|
|
136
|
+
case 4: {
|
|
137
|
+
const p = a + b - c;
|
|
138
|
+
const pa = Math.abs(p - a);
|
|
139
|
+
const pb = Math.abs(p - b);
|
|
140
|
+
const pc = Math.abs(p - c);
|
|
141
|
+
value = x + (pa <= pb && pa <= pc ? a : pb <= pc ? b : c);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
default:
|
|
145
|
+
return { reason: `malformed PNG: scanline filter ${filter} at row ${y}` };
|
|
146
|
+
}
|
|
147
|
+
pixels[outRow + i] = value & 0xff;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { width, height, channels, pixels };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The ink extent of a capture, read `insetPx` in from every edge — the judge's inset skips a host
|
|
155
|
+
* panel's gap, rounded corners and hairline ({@link JUDGE_INK_INSET_PX}); `0` reads the whole image.
|
|
156
|
+
*/
|
|
157
|
+
export function readInkExtent(png: Uint8Array, insetPx = 0): InkExtent | InkUnreadable {
|
|
158
|
+
const image = decodePng(png);
|
|
159
|
+
if ('reason' in image) return image;
|
|
160
|
+
const region: PixelRegion = { x: insetPx, y: insetPx, width: image.width - 2 * insetPx, height: image.height - 2 * insetPx };
|
|
161
|
+
if (insetPx < 0 || region.width <= 0 || region.height <= 0) {
|
|
162
|
+
return { reason: `an inset of ${insetPx}px leaves nothing of a ${image.width}×${image.height} image` };
|
|
163
|
+
}
|
|
164
|
+
const { channels, pixels, width } = image;
|
|
165
|
+
// The ground: the region's most common colour.
|
|
166
|
+
const counts = new Map<number, number>();
|
|
167
|
+
let ground = 0;
|
|
168
|
+
let groundCount = 0;
|
|
169
|
+
for (let y = region.y; y < region.y + region.height; y++) {
|
|
170
|
+
for (let x = region.x; x < region.x + region.width; x++) {
|
|
171
|
+
const i = (y * width + x) * channels;
|
|
172
|
+
const key = (pixels[i]! << 16) | (pixels[i + 1]! << 8) | pixels[i + 2]!;
|
|
173
|
+
const n = (counts.get(key) ?? 0) + 1;
|
|
174
|
+
counts.set(key, n);
|
|
175
|
+
if (n > groundCount) {
|
|
176
|
+
groundCount = n;
|
|
177
|
+
ground = key;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const gr = (ground >> 16) & 0xff;
|
|
182
|
+
const gg = (ground >> 8) & 0xff;
|
|
183
|
+
const gb = ground & 0xff;
|
|
184
|
+
let lastInkRow: number | null = null;
|
|
185
|
+
for (let y = region.y; y < region.y + region.height; y++) {
|
|
186
|
+
for (let x = region.x; x < region.x + region.width; x++) {
|
|
187
|
+
const i = (y * width + x) * channels;
|
|
188
|
+
if (
|
|
189
|
+
Math.abs(pixels[i]! - gr) > INK_TOLERANCE ||
|
|
190
|
+
Math.abs(pixels[i + 1]! - gg) > INK_TOLERANCE ||
|
|
191
|
+
Math.abs(pixels[i + 2]! - gb) > INK_TOLERANCE
|
|
192
|
+
) {
|
|
193
|
+
lastInkRow = y - region.y;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const ratio = lastInkRow === null ? 0 : Math.round(((lastInkRow + 1) / region.height) * 1000) / 1000;
|
|
199
|
+
return { ratio, lastInkRow, region, ground: [gr, gg, gb] };
|
|
200
|
+
}
|
|
@@ -5,10 +5,17 @@ import { composeThemeCss, getCssTokens } from '@ggui-ai/design/rendering';
|
|
|
5
5
|
* The stylesheet a visual judge renders under for an app: the app's theme
|
|
6
6
|
* composed the way the runtime composes it (`composeThemeCss`, layer `page`
|
|
7
7
|
* — one composition, never a second), so a judged canvas paints the tokens a
|
|
8
|
-
* visitor's page paints.
|
|
9
|
-
*
|
|
8
|
+
* visitor's page paints. `themeId` is the registered ladder the app's theme
|
|
9
|
+
* sits on (the runtime's base layer); an unknown id composes the default
|
|
10
|
+
* ladder. Neither given ⇒ the design's default tokens, byte-for-byte what the
|
|
11
|
+
* judge used before.
|
|
10
12
|
*/
|
|
11
|
-
export function cssTokensForAppTheme(theme: AppTheme | undefined, mode: 'light' | 'dark' = 'light'): string {
|
|
12
|
-
if (theme === undefined) return getCssTokens(mode);
|
|
13
|
-
return composeThemeCss({
|
|
13
|
+
export function cssTokensForAppTheme(theme: AppTheme | undefined, mode: 'light' | 'dark' = 'light', themeId?: string): string {
|
|
14
|
+
if (theme === undefined && themeId === undefined) return getCssTokens(mode);
|
|
15
|
+
return composeThemeCss({
|
|
16
|
+
layer: 'page',
|
|
17
|
+
mode: theme?.mode ?? mode,
|
|
18
|
+
...(themeId !== undefined ? { themeId } : {}),
|
|
19
|
+
...(theme !== undefined ? { appTheme: theme } : {}),
|
|
20
|
+
});
|
|
14
21
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
export type Priority = "P0" | "P1" | "P2";
|
|
22
22
|
|
|
23
|
+
import type { GenerationRuntimeProbeOutcome, GenerationRuntimeProbeStatus } from "@ggui-ai/mcp-server-core";
|
|
23
24
|
import type { CanvasClass, DesignMode } from "../design-mode.js";
|
|
24
25
|
|
|
25
26
|
// ─── Issue shape ───────────────────────────────────────────────────────────
|
|
@@ -63,8 +64,10 @@ export interface EvalIssue {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
|
-
* Runtime-render probe execution status —
|
|
67
|
-
* never ran" is never conflated with "the probe ran clean".
|
|
67
|
+
* Runtime-render probe execution status — four-valued so "the probe
|
|
68
|
+
* never ran" is never conflated with "the probe ran clean". ONE union: the
|
|
69
|
+
* same values a generation's metadata reports (`GenerationRuntimeProbeStatus`
|
|
70
|
+
* in `@ggui-ai/mcp-server-core`), declared there and named here.
|
|
68
71
|
*
|
|
69
72
|
* - `ran` — probe executed; its findings (possibly none) are
|
|
70
73
|
* in the issue stream under `runtime:*` subcategories.
|
|
@@ -83,7 +86,7 @@ export interface EvalIssue {
|
|
|
83
86
|
* `hostLoad` say how long it ran and how busy the
|
|
84
87
|
* host was, so a reader can tell the two cases apart.
|
|
85
88
|
*/
|
|
86
|
-
export type RuntimeProbeStatus =
|
|
89
|
+
export type RuntimeProbeStatus = GenerationRuntimeProbeStatus;
|
|
87
90
|
|
|
88
91
|
/**
|
|
89
92
|
* The host's 1-minute load average at a probe's start and end, beside the
|
|
@@ -98,21 +101,76 @@ export interface ProbeHostLoad {
|
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
104
|
+
* The engine's own fields on a probe's meta, beside the outcome a
|
|
105
|
+
* generation's metadata reports: why a probe did not finish, its render
|
|
106
|
+
* time, the host's load around it. Present on every arm of
|
|
107
|
+
* {@link RuntimeProbeMeta}; each is absent when the probe had nothing to
|
|
108
|
+
* say about it.
|
|
105
109
|
*/
|
|
106
|
-
export interface
|
|
107
|
-
readonly status: RuntimeProbeStatus;
|
|
110
|
+
export interface RuntimeProbeMetaDetail {
|
|
108
111
|
/** Populated for `infra-skipped` / `not-applicable` / `timed-out` — why the probe didn't finish. */
|
|
109
112
|
readonly reason?: string;
|
|
110
|
-
/** `
|
|
111
|
-
readonly
|
|
113
|
+
/** `ran` only: the check's own render time, inside `elapsedMs`. */
|
|
114
|
+
readonly renderMs?: number;
|
|
112
115
|
/** Host load around the probe, when it ran isolated (absent for an in-process probe). */
|
|
113
116
|
readonly hostLoad?: ProbeHostLoad;
|
|
117
|
+
/** Time the check waited for a probe slot before it started, ms; present only when > 0 (ggui#1380 C2b). */
|
|
118
|
+
readonly queuedMs?: number;
|
|
114
119
|
}
|
|
115
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Probe execution meta stamped onto an `EvalResult` at the exit-decision
|
|
123
|
+
* points that invoke the probe. Absence of this field means the eval
|
|
124
|
+
* path never reached a probe invocation at all — consumers treat that
|
|
125
|
+
* the same as not-run, never as a pass.
|
|
126
|
+
*
|
|
127
|
+
* ONE union with the generation metadata's probe outcome
|
|
128
|
+
* (`GenerationRuntimeProbeOutcome` in `@ggui-ai/mcp-server-core`, declared
|
|
129
|
+
* there and carried here with {@link RuntimeProbeMetaDetail} on every arm),
|
|
130
|
+
* so the round's stamp and the metadata's record cannot say different
|
|
131
|
+
* things (ggui#1380):
|
|
132
|
+
*
|
|
133
|
+
* - `ran` + `verdict: 'pass'` — the probe executed and no check failed;
|
|
134
|
+
* - `ran` + `verdict: 'fail'` — ANY check failed; `failChecks` lists the
|
|
135
|
+
* distinct check kinds that did (`RenderCheckKind`, the engine's name
|
|
136
|
+
* for the same union), never empty, in the order `RENDER_CHECK_KINDS`
|
|
137
|
+
* declares them, parsed from the probe's own
|
|
138
|
+
* `runtime:<check>[:<subject>]` issues;
|
|
139
|
+
* - `timed-out` / `infra-skipped` / `not-applicable` — no verdict, and the
|
|
140
|
+
* type carries none. `elapsedMs` (on every status that reached the
|
|
141
|
+
* check) and `hostLoad` say how long it ran and how busy the host was.
|
|
142
|
+
*
|
|
143
|
+
* The verdict and the repair trigger are two different questions. The
|
|
144
|
+
* verdict is `fail` on any failing check. The one repair turn a probe-only
|
|
145
|
+
* round buys fires on a `render-no-throw` fail the engine recognises
|
|
146
|
+
* (`isRecoverableRenderCrash`) and on nothing else: on the PROBE-ONLY round a
|
|
147
|
+
* `fail` verdict whose checks are `prop-sensitivity`, `action-wiring` or any
|
|
148
|
+
* other kind is recorded here and never fed back. The same meta is stamped on
|
|
149
|
+
* the EVALUATION lane, where a `prop-sensitivity` fail buys that lane's own
|
|
150
|
+
* contract-feedback round (ggui#1261, `FEEDBACK_PROBE_CHECKS`) — a different
|
|
151
|
+
* turn under a different rule, not the repair turn.
|
|
152
|
+
*/
|
|
153
|
+
export type RuntimeProbeMeta = GenerationRuntimeProbeOutcome & RuntimeProbeMetaDetail;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The one repair turn a probe-only round buys (ggui#1380). A serving
|
|
157
|
+
* deployment that wires the runtime probe and configures no evaluator runs
|
|
158
|
+
* the probe once after the coding turns; a render crash the engine
|
|
159
|
+
* recognises buys exactly one repair turn, then one re-probe. This records
|
|
160
|
+
* what came of it:
|
|
161
|
+
* - `compiled: false` — the repair turn's code did not pass self-check, so
|
|
162
|
+
* no re-probe ran (there is no `after`) and the pre-repair card is the
|
|
163
|
+
* one served;
|
|
164
|
+
* - `compiled: true` — the re-probe ran and `after` is its whole meta:
|
|
165
|
+
* its status, its verdict when it ran, and the checks that still fail.
|
|
166
|
+
* Whether the crash class the turn was bought for is still there is
|
|
167
|
+
* `after.verdict === 'fail' && after.failChecks.includes('render-no-throw')`.
|
|
168
|
+
* The two arms are exclusive by type.
|
|
169
|
+
*/
|
|
170
|
+
export type RuntimeProbeRepair =
|
|
171
|
+
| { readonly attempted: true; readonly compiled: false; readonly trigger?: never }
|
|
172
|
+
| { readonly attempted: true; readonly compiled: true; readonly trigger: RuntimeProbeMeta };
|
|
173
|
+
|
|
116
174
|
/**
|
|
117
175
|
* Per-criterion execution status — three-valued so "the criterion
|
|
118
176
|
* silently produced no verdict" is never conflated with "the criterion
|
|
@@ -226,6 +284,13 @@ export interface EvalResult {
|
|
|
226
284
|
* probe runner; absent on eval paths that never invoke the probe.
|
|
227
285
|
*/
|
|
228
286
|
runtimeProbe?: RuntimeProbeMeta;
|
|
287
|
+
/**
|
|
288
|
+
* The probe-only round's repair record (ggui#1380) — present only when a
|
|
289
|
+
* recoverable render crash bought the one repair turn. See
|
|
290
|
+
* {@link RuntimeProbeRepair}. `runtimeProbe` beside it is the re-probe's
|
|
291
|
+
* verdict when the repair compiled, else the pre-repair probe's.
|
|
292
|
+
*/
|
|
293
|
+
runtimeProbeRepair?: RuntimeProbeRepair;
|
|
229
294
|
/**
|
|
230
295
|
* The contract-feedback round, when one fired this generation (ggui#1261):
|
|
231
296
|
* which exit-probe findings bought it and the source as it stood when the
|
|
@@ -271,6 +336,108 @@ export interface CanvasJudgeRecord {
|
|
|
271
336
|
readonly notes: string[];
|
|
272
337
|
}
|
|
273
338
|
|
|
339
|
+
// ─── Visual criteria (ggui#1436) ──────────────────────────────────────────
|
|
340
|
+
// The judge's typed criteria block per canvas — report-only beside the score
|
|
341
|
+
// until a later word flips a gate to it. Every field below is optional on the
|
|
342
|
+
// stamps so yesterday's `eval.json` and an old judge answer still parse (N−1).
|
|
343
|
+
import type { FetchShape, LayoutShape, RenderShape, RiskTier, StateShape, WriteShape } from "../classifier/axes.js";
|
|
344
|
+
|
|
345
|
+
export type CriteriaSeverity = "must" | "should";
|
|
346
|
+
export type CriteriaVerdict = "pass" | "fail" | "n/a";
|
|
347
|
+
/** The method that produces a verdict: an instrument's measurement, the vision judge off the frame, or a person's eye. */
|
|
348
|
+
export type CriteriaMethod = "instrument" | "judge" | "human";
|
|
349
|
+
/** The method's readiness, as the bank's author wrote it — the only source of an `n/a` that is not the judge's own. */
|
|
350
|
+
export type CriteriaStatus = "live" | "reference" | "planned" | "manual";
|
|
351
|
+
/**
|
|
352
|
+
* How a criterion entered the selection: bank-level (`static`, an empty scope) or by the selector's
|
|
353
|
+
* rules for this card (`context`). A model PROPOSING a criterion at judge time is RESERVED and not a
|
|
354
|
+
* member until a consumer exists — it would be `should`-only, recorded verbatim, never a gate.
|
|
355
|
+
*/
|
|
356
|
+
export type CriteriaSource = "static" | "context";
|
|
357
|
+
export type CriteriaChroma = "achromatic" | "chromatic" | "unknown";
|
|
358
|
+
/** The classifier's shape axes, as hashed into the set id. */
|
|
359
|
+
export interface CriteriaAxes {
|
|
360
|
+
render: RenderShape;
|
|
361
|
+
state: StateShape;
|
|
362
|
+
writes: WriteShape;
|
|
363
|
+
fetch: FetchShape;
|
|
364
|
+
layout: LayoutShape;
|
|
365
|
+
}
|
|
366
|
+
/** The selector's inputs, stored with the verdict so a reader recomputes `criteriaSetId` from the record alone. */
|
|
367
|
+
export interface CriteriaContext {
|
|
368
|
+
canvas: CanvasClass;
|
|
369
|
+
hasActions: boolean;
|
|
370
|
+
riskTier: RiskTier;
|
|
371
|
+
axes: CriteriaAxes;
|
|
372
|
+
chroma: CriteriaChroma;
|
|
373
|
+
profilePresent: boolean;
|
|
374
|
+
/** The harness's shell as named; `unknown` when the round carries none. */
|
|
375
|
+
shell: string;
|
|
376
|
+
/** The caller's item kind (a bootstrap mint's `hello`); hashed when present. */
|
|
377
|
+
kind?: string;
|
|
378
|
+
/** The styling preset in force (the profile's aesthetic id); a recipe is selected only under its preset. */
|
|
379
|
+
preset?: string;
|
|
380
|
+
}
|
|
381
|
+
/** What was asked and why — recorded BEFORE the judge runs. Omission is the selector's decision. */
|
|
382
|
+
export interface CriteriaSelection {
|
|
383
|
+
id: string;
|
|
384
|
+
source: CriteriaSource;
|
|
385
|
+
reason: string;
|
|
386
|
+
}
|
|
387
|
+
/** One criterion's verdict; `n/a` is the judge's answer ("cannot read this off the frame"), an unanswered id, a tie, or a method whose `status` is not live. */
|
|
388
|
+
export interface CriterionVerdict {
|
|
389
|
+
id: string;
|
|
390
|
+
severity: CriteriaSeverity;
|
|
391
|
+
method: CriteriaMethod;
|
|
392
|
+
status: CriteriaStatus;
|
|
393
|
+
source: CriteriaSource;
|
|
394
|
+
verdict: CriteriaVerdict;
|
|
395
|
+
evidence: string;
|
|
396
|
+
}
|
|
397
|
+
export interface CriteriaBlock {
|
|
398
|
+
/** sha256(bank version | selector version | canonical context), 16 hex — the block's own digest. */
|
|
399
|
+
criteriaSetId: string;
|
|
400
|
+
bankVersion: string;
|
|
401
|
+
selectorVersion: string;
|
|
402
|
+
context: CriteriaContext;
|
|
403
|
+
selection: CriteriaSelection[];
|
|
404
|
+
verdicts: CriterionVerdict[];
|
|
405
|
+
}
|
|
406
|
+
/** The roll-up across canvases; a `must` at `n/a` is listed, never counted as a pass. */
|
|
407
|
+
export interface CriteriaRollup {
|
|
408
|
+
mustFailed: string[];
|
|
409
|
+
shouldFailed: string[];
|
|
410
|
+
mustNa: string[];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** ggui#1492 — a host"s frame round the inline card, as data. Colours are `#rrggbb`; the ring"s `alpha` is 0..1. */
|
|
414
|
+
export interface HostInlineFrame {
|
|
415
|
+
/** The frame"s surface — what shows behind a transparent card, and under the floor"s residual. */
|
|
416
|
+
readonly surface: string;
|
|
417
|
+
/** The room the frame sits in (the page ground round it). */
|
|
418
|
+
readonly ground: string;
|
|
419
|
+
readonly ring: { readonly widthPx: number; readonly color: string; readonly alpha: number };
|
|
420
|
+
readonly radiusPx: number;
|
|
421
|
+
/** The host"s minimum frame height: a card shorter than it sits in a frame this tall, and the visitor sees the rest as void. */
|
|
422
|
+
readonly minHeightPx?: number;
|
|
423
|
+
}
|
|
424
|
+
/** ggui#1492 — how a host presents one canvas: the label the judge is told, the page ground, and (the inline card only) its frame. */
|
|
425
|
+
export interface CanvasHostPresentation {
|
|
426
|
+
readonly label: string;
|
|
427
|
+
/** The page ground under a fill canvas"s panel; the inline card"s ground lives on its {@link HostInlineFrame}. */
|
|
428
|
+
readonly ground?: string;
|
|
429
|
+
readonly frame?: HostInlineFrame;
|
|
430
|
+
}
|
|
431
|
+
/** ggui#1492 — why the judge did not draw a canvas's host presentation. The codes are the lane reader's own. */
|
|
432
|
+
export type HostPresentationIgnoredReason = "label_missing" | "color_not_hex" | "frame_off_inline" | "frame_malformed" | "ground_on_inline";
|
|
433
|
+
/**
|
|
434
|
+
* ggui#1492 — what the judge DID with a canvas's host presentation, read from the judge itself: `applied` carries
|
|
435
|
+
* the presentation it drew, `ignored` the reason it drew none. Absent when the caller supplied no presentation.
|
|
436
|
+
*/
|
|
437
|
+
export type CanvasPresentationOutcome =
|
|
438
|
+
| { readonly status: "applied"; readonly applied: CanvasHostPresentation }
|
|
439
|
+
| { readonly status: "ignored"; readonly reason: HostPresentationIgnoredReason };
|
|
440
|
+
|
|
274
441
|
export interface CanvasVisualSummary {
|
|
275
442
|
canvas: CanvasClass;
|
|
276
443
|
viewport: { width: number; height: number };
|
|
@@ -282,8 +449,19 @@ export interface CanvasVisualSummary {
|
|
|
282
449
|
contentHeight: number | null;
|
|
283
450
|
/** `contentHeight > viewport.height` — measured on every canvas, judged per `canvasFitPolicy`. */
|
|
284
451
|
overflow: boolean;
|
|
452
|
+
/**
|
|
453
|
+
* ggui#1120 — how far down the capture anything was painted: the last inked row over the measured
|
|
454
|
+
* region's height (inside a drawn host panel's chrome on md/lg/xl), read from the screenshot's
|
|
455
|
+
* pixels against their dominant colour. `0` is the blank the judge fails (`canvas-blank`); every
|
|
456
|
+
* other value is reported, never scored. `null` when the capture could not be read.
|
|
457
|
+
*/
|
|
458
|
+
inkRatio: number | null;
|
|
285
459
|
/** How `score` was reached — always present (ggui#1072). */
|
|
286
460
|
judge: CanvasJudgeRecord;
|
|
461
|
+
/** ggui#1436 — the typed criteria block, when a bank was configured; report-only. */
|
|
462
|
+
criteria?: CriteriaBlock;
|
|
463
|
+
/** ggui#1492 — what the judge did with this canvas's host presentation, when one was supplied. */
|
|
464
|
+
presentation?: CanvasPresentationOutcome;
|
|
287
465
|
/**
|
|
288
466
|
* How the judge composed the mount (ggui#1100): `'fill'` on every
|
|
289
467
|
* fullscreen canvas — the served runtime's fit, the root stretched to
|
|
@@ -320,6 +498,8 @@ export interface VisualEvalSummary {
|
|
|
320
498
|
design?: { readonly src: string; readonly srcSha256: string };
|
|
321
499
|
/** The mode the judge's tokens were composed in, when the caller said (ggui#1076). */
|
|
322
500
|
themeMode?: 'light' | 'dark';
|
|
501
|
+
/** ggui#1436 — the criteria roll-up across canvases, when any canvas carries a block; report-only. */
|
|
502
|
+
criteria?: CriteriaRollup;
|
|
323
503
|
}
|
|
324
504
|
|
|
325
505
|
// ─── Quality mode ──────────────────────────────────────────────────────────
|
|
@@ -345,6 +525,7 @@ export const DEFAULT_QUALITY_CONFIG: QualityConfig = {
|
|
|
345
525
|
// `./axis-checks/`.
|
|
346
526
|
|
|
347
527
|
import type { DataContract } from "@ggui-ai/protocol";
|
|
528
|
+
import type { JsonObject } from "@ggui-ai/protocol";
|
|
348
529
|
import type { AxisVector, Classification } from "../classifier/axes.js";
|
|
349
530
|
import type { AxisKey } from "../fragments/index.js";
|
|
350
531
|
|
|
@@ -371,6 +552,14 @@ export interface AxisCheckInput {
|
|
|
371
552
|
* default (a silent `lg` would be the same guess with a nicer face).
|
|
372
553
|
*/
|
|
373
554
|
canvas?: CanvasClass;
|
|
555
|
+
/**
|
|
556
|
+
* The props the probe and the judge RENDERED the source with (ggui#1285) — a bootstrap order's
|
|
557
|
+
* `sampleProps`, the bench commit's fixture. They are judging data, not the request: a literal in
|
|
558
|
+
* the source that equals one of these values, and that the request never stated, is baked copy a
|
|
559
|
+
* visitor sees whenever the agent leaves the prop out. Populated on the served loop and at
|
|
560
|
+
* auto-commit; ABSENT where the caller has none — the check stands down, it never guesses.
|
|
561
|
+
*/
|
|
562
|
+
fixtureProps?: JsonObject;
|
|
374
563
|
}
|
|
375
564
|
|
|
376
565
|
/**
|
package/src/evaluation/types.ts
CHANGED
|
@@ -33,6 +33,13 @@ export interface EvaluationIssue {
|
|
|
33
33
|
/**
|
|
34
34
|
* Result of a single evaluation round.
|
|
35
35
|
*/
|
|
36
|
+
/** One judge answer to a criterion (ggui#1436), as parsed — before the K-majority. */
|
|
37
|
+
export interface CriteriaAnswer {
|
|
38
|
+
id: string;
|
|
39
|
+
verdict: 'pass' | 'fail' | 'n/a';
|
|
40
|
+
evidence: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
export interface EvaluationResult {
|
|
37
44
|
/** Whether the component passed the quality threshold */
|
|
38
45
|
passed: boolean;
|
|
@@ -48,6 +55,8 @@ export interface EvaluationResult {
|
|
|
48
55
|
inputTokens?: number;
|
|
49
56
|
/** Output tokens consumed by the evaluation LLM call */
|
|
50
57
|
outputTokens?: number;
|
|
58
|
+
/** ggui#1436 — the judge's criteria answers, when the prompt asked for them; never part of the score. */
|
|
59
|
+
criteriaAnswers?: CriteriaAnswer[];
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
/**
|