@ggui-ai/ui-gen 0.28.0 → 0.29.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-h4_V69LI.d.ts → adapter-DLQ-5COm.d.ts} +1 -1
- package/dist/adapters/base.js +2 -27
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/generation-dispatch.d.ts +14 -6
- package/dist/adapters/generation-dispatch.js +40 -19260
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +25 -3561
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/types.js +2 -9
- package/dist/adapters/types.js.map +1 -1
- package/dist/advanced/index.d.ts +13 -12
- package/dist/advanced/index.js +63 -21954
- package/dist/advanced/index.js.map +1 -1
- package/dist/{bank-Ctdp3FUA.d.ts → bank-LWaiAtaA.d.ts} +1 -1
- package/dist/blueprint-validator.js +6 -2301
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +3 -2
- package/dist/boilerplate.js +6 -1867
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +6 -1923
- package/dist/check/index.js.map +1 -1
- package/dist/chunk-33B3BAWS.js +24 -0
- package/dist/chunk-33B3BAWS.js.map +1 -0
- package/dist/chunk-3HSGA6NI.js +591 -0
- package/dist/chunk-3HSGA6NI.js.map +1 -0
- package/dist/chunk-3NNNGNTO.js +1707 -0
- package/dist/chunk-3NNNGNTO.js.map +1 -0
- package/dist/chunk-3T2HMTON.js +79 -0
- package/dist/chunk-3T2HMTON.js.map +1 -0
- package/dist/chunk-3ZSSDV3L.js +123 -0
- package/dist/chunk-3ZSSDV3L.js.map +1 -0
- package/dist/chunk-4MBR5GA3.js +99 -0
- package/dist/chunk-4MBR5GA3.js.map +1 -0
- package/dist/chunk-56NJFD55.js +337 -0
- package/dist/chunk-56NJFD55.js.map +1 -0
- package/dist/chunk-5AGTY555.js +26 -0
- package/dist/chunk-5AGTY555.js.map +1 -0
- package/dist/chunk-5EJM7EGY.js +115 -0
- package/dist/chunk-5EJM7EGY.js.map +1 -0
- package/dist/chunk-5KXCQLLR.js +529 -0
- package/dist/chunk-5KXCQLLR.js.map +1 -0
- package/dist/chunk-5VBXHVEH.js +171 -0
- package/dist/chunk-5VBXHVEH.js.map +1 -0
- package/dist/chunk-6KDDEING.js +515 -0
- package/dist/chunk-6KDDEING.js.map +1 -0
- package/dist/chunk-6VDRWAP6.js +123 -0
- package/dist/chunk-6VDRWAP6.js.map +1 -0
- package/dist/chunk-732IBFEJ.js +2400 -0
- package/dist/chunk-732IBFEJ.js.map +1 -0
- package/dist/chunk-73AURS3I.js +46 -0
- package/dist/chunk-73AURS3I.js.map +1 -0
- package/dist/chunk-77H5SKLX.js +8 -0
- package/dist/chunk-77H5SKLX.js.map +1 -0
- package/dist/chunk-7QE3AHLE.js +35 -0
- package/dist/chunk-7QE3AHLE.js.map +1 -0
- package/dist/chunk-A375NNWN.js +11 -0
- package/dist/chunk-A375NNWN.js.map +1 -0
- package/dist/chunk-AOU33EFS.js +140 -0
- package/dist/chunk-AOU33EFS.js.map +1 -0
- package/dist/chunk-BA4QYA6E.js +461 -0
- package/dist/chunk-BA4QYA6E.js.map +1 -0
- package/dist/chunk-DBP764GL.js +3 -0
- package/dist/chunk-DBP764GL.js.map +1 -0
- package/dist/chunk-DUU3ZJDQ.js +41 -0
- package/dist/chunk-DUU3ZJDQ.js.map +1 -0
- package/dist/chunk-DXKRBXNU.js +30 -0
- package/dist/chunk-DXKRBXNU.js.map +1 -0
- package/dist/chunk-FB7OEEAH.js +1467 -0
- package/dist/chunk-FB7OEEAH.js.map +1 -0
- package/dist/chunk-FLRNMZV2.js +18 -0
- package/dist/chunk-FLRNMZV2.js.map +1 -0
- package/dist/chunk-FWJIMKFZ.js +2032 -0
- package/dist/chunk-FWJIMKFZ.js.map +1 -0
- package/dist/chunk-FXRHWYIQ.js +163 -0
- package/dist/chunk-FXRHWYIQ.js.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/chunk-GAJI7Y3P.js +13 -0
- package/dist/chunk-GAJI7Y3P.js.map +1 -0
- package/dist/chunk-GYINWMPR.js +2052 -0
- package/dist/chunk-GYINWMPR.js.map +1 -0
- package/dist/chunk-HD6ABIK7.js +484 -0
- package/dist/chunk-HD6ABIK7.js.map +1 -0
- package/dist/chunk-HDQVHYO6.js +100 -0
- package/dist/chunk-HDQVHYO6.js.map +1 -0
- package/dist/chunk-IHUT3SIW.js +7 -0
- package/dist/chunk-IHUT3SIW.js.map +1 -0
- package/dist/chunk-JDH5EGNZ.js +126 -0
- package/dist/chunk-JDH5EGNZ.js.map +1 -0
- package/dist/chunk-JMD4RGSV.js +37 -0
- package/dist/chunk-JMD4RGSV.js.map +1 -0
- package/dist/chunk-KJWGZL5B.js +33 -0
- package/dist/chunk-KJWGZL5B.js.map +1 -0
- package/dist/chunk-MKMDQFZ7.js +86 -0
- package/dist/chunk-MKMDQFZ7.js.map +1 -0
- package/dist/chunk-NF6JUZWF.js +85 -0
- package/dist/chunk-NF6JUZWF.js.map +1 -0
- package/dist/chunk-O2YC6XDC.js +1933 -0
- package/dist/chunk-O2YC6XDC.js.map +1 -0
- package/dist/chunk-OVLH5ICV.js +29 -0
- package/dist/chunk-OVLH5ICV.js.map +1 -0
- package/dist/chunk-PBOYHGSD.js +106 -0
- package/dist/chunk-PBOYHGSD.js.map +1 -0
- package/dist/chunk-PJFH7QWA.js +81 -0
- package/dist/chunk-PJFH7QWA.js.map +1 -0
- package/dist/chunk-PUH4CXGZ.js +284 -0
- package/dist/chunk-PUH4CXGZ.js.map +1 -0
- package/dist/chunk-QLXXPGTV.js +18 -0
- package/dist/chunk-QLXXPGTV.js.map +1 -0
- package/dist/chunk-R5GIGPW2.js +2845 -0
- package/dist/chunk-R5GIGPW2.js.map +1 -0
- package/dist/chunk-RH4IUSTO.js +83 -0
- package/dist/chunk-RH4IUSTO.js.map +1 -0
- package/dist/chunk-S2GVXKKY.js +153 -0
- package/dist/chunk-S2GVXKKY.js.map +1 -0
- package/dist/chunk-TVHGKFJS.js +58 -0
- package/dist/chunk-TVHGKFJS.js.map +1 -0
- package/dist/chunk-U7GALWLG.js +66 -0
- package/dist/chunk-U7GALWLG.js.map +1 -0
- package/dist/chunk-VTC263LT.js +256 -0
- package/dist/chunk-VTC263LT.js.map +1 -0
- package/dist/chunk-WEOXFEVE.js +739 -0
- package/dist/chunk-WEOXFEVE.js.map +1 -0
- package/dist/chunk-WYHP46LT.js +28 -0
- package/dist/chunk-WYHP46LT.js.map +1 -0
- package/dist/chunk-XGLO5LGG.js +274 -0
- package/dist/chunk-XGLO5LGG.js.map +1 -0
- package/dist/chunk-XRBCRKA2.js +29 -0
- package/dist/chunk-XRBCRKA2.js.map +1 -0
- package/dist/chunk-XW7ADFFV.js +340 -0
- package/dist/chunk-XW7ADFFV.js.map +1 -0
- package/dist/chunk-YIQ42IUX.js +34 -0
- package/dist/chunk-YIQ42IUX.js.map +1 -0
- package/dist/chunk-YJZTUY7B.js +101 -0
- package/dist/chunk-YJZTUY7B.js.map +1 -0
- package/dist/chunk-Z3CXME3K.js +107 -0
- package/dist/chunk-Z3CXME3K.js.map +1 -0
- package/dist/classifier/index.js +2 -527
- package/dist/classifier/index.js.map +1 -1
- package/dist/client-JBPCXU7W.js +4 -0
- package/dist/client-JBPCXU7W.js.map +1 -0
- package/dist/compose.js +3 -367
- package/dist/compose.js.map +1 -1
- package/dist/cost-tracker-HHGQ4FTW.js +67 -0
- package/dist/cost-tracker-HHGQ4FTW.js.map +1 -0
- package/dist/{create-ui-generator-BrZHF0cf.d.ts → create-ui-generator-BY-WeaPi.d.ts} +3 -3
- package/dist/{design-mode-DGWN_Gkm.d.ts → design-mode-XyOQmzE0.d.ts} +4 -3
- package/dist/{dispatch-B07TI30d.d.ts → dispatch-8LzTddKw.d.ts} +3 -2
- package/dist/dist-UALN6ZVB.js +543 -0
- package/dist/dist-UALN6ZVB.js.map +1 -0
- package/dist/dupe-break-X6K7QFIS.js +4 -0
- package/dist/dupe-break-X6K7QFIS.js.map +1 -0
- package/dist/evaluation/axis-checks/index.d.ts +5 -4
- package/dist/evaluation/axis-checks/index.js +6 -1914
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +3 -2
- package/dist/evaluation/axis-checks/registry.js +3 -1844
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +49 -132
- package/dist/evaluation/index.js +302 -6466
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +3 -2
- package/dist/evaluation/types.js +2 -5
- package/dist/evaluation/types.js.map +1 -1
- package/dist/fragments/index.js +2 -338
- package/dist/fragments/index.js.map +1 -1
- package/dist/{generate-tSO_shQ0.d.ts → generate-DQ4ci90s.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +35 -15
- package/dist/harness/check/runtime-render/index.js +6 -2390
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +4 -1739
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +3 -2
- package/dist/harness/index.js +23 -5126
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.js +2 -24
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -2
- package/dist/harness/prompts.js +8 -324
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +6 -5
- package/dist/harness/result-types.js +2 -0
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +22 -5
- package/dist/harness/runtime.js +10 -2313
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +3 -2
- package/dist/harness/types-public.js +16 -2954
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +3 -2
- package/dist/harness/validator-trace-sink.js +2 -26
- package/dist/harness/validator-trace-sink.js.map +1 -1
- package/dist/hash.js +2 -39
- package/dist/hash.js.map +1 -1
- package/dist/hashline-5OKREIRM.js +4 -0
- package/dist/hashline-5OKREIRM.js.map +1 -0
- package/dist/index.d.ts +50 -11
- package/dist/index.js +57 -22015
- package/dist/index.js.map +1 -1
- package/dist/llm-evaluator-PN3WFCS4.js +653 -0
- package/dist/llm-evaluator-PN3WFCS4.js.map +1 -0
- package/dist/magic-string.es-2NZTC4GZ.js +1307 -0
- package/dist/magic-string.es-2NZTC4GZ.js.map +1 -0
- package/dist/patch.d.ts +3 -2
- package/dist/patch.js +2 -56
- package/dist/patch.js.map +1 -1
- package/dist/policy-VCYKGSAC.js +5 -0
- package/dist/policy-VCYKGSAC.js.map +1 -0
- package/dist/policy.js +2 -35
- package/dist/policy.js.map +1 -1
- package/dist/primitive-index-2OF6F4TZ.js +131 -0
- package/dist/primitive-index-2OF6F4TZ.js.map +1 -0
- package/dist/primitive-slice-WTMBLYFJ.js +235 -0
- package/dist/primitive-slice-WTMBLYFJ.js.map +1 -0
- package/dist/{prompts-BhNUuRoZ.d.ts → prompts-CLZGywMP.d.ts} +1 -1
- package/dist/provider-adapter-contract.js +10407 -14204
- package/dist/provider-adapter-contract.js.map +1 -1
- package/dist/provider-adapter.js +2 -44
- package/dist/provider-adapter.js.map +1 -1
- package/dist/providers/index.d.ts +89 -1
- package/dist/providers/index.js +115 -44
- package/dist/providers/index.js.map +1 -1
- package/dist/raw-C6KBH5I3.js +141 -0
- package/dist/raw-C6KBH5I3.js.map +1 -0
- package/dist/raw-FXSBXQTZ.js +7 -0
- package/dist/raw-FXSBXQTZ.js.map +1 -0
- package/dist/raw-MURDUPGK.js +8 -0
- package/dist/raw-MURDUPGK.js.map +1 -0
- package/dist/raw-T3GW3UVX.js +8 -0
- package/dist/raw-T3GW3UVX.js.map +1 -0
- package/dist/render-check-host-ZNIS6BJ5.js +5 -0
- package/dist/render-check-host-ZNIS6BJ5.js.map +1 -0
- package/dist/sdk-4JNSDNSR.js +6 -0
- package/dist/sdk-4JNSDNSR.js.map +1 -0
- package/dist/sdk-DIVJMHCX.js +7 -0
- package/dist/sdk-DIVJMHCX.js.map +1 -0
- package/dist/sdk-QRJOCRTS.js +7 -0
- package/dist/sdk-QRJOCRTS.js.map +1 -0
- package/dist/tag-balance-CTHCTRJ7.js +123 -0
- package/dist/tag-balance-CTHCTRJ7.js.map +1 -0
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +3 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/tools/render-check-worker.js +3 -40
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-h8IMnYuh.d.ts → types-public-hYlrerZh.d.ts} +35 -2
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +3 -498
- package/dist/validation/index.js.map +1 -1
- package/dist/{visual-evaluator-C2strxqD.d.ts → visual-evaluator-KskajxD5.d.ts} +14 -4
- package/dist/visual-evaluator-TGEXYQTP.js +12 -0
- package/dist/visual-evaluator-TGEXYQTP.js.map +1 -0
- package/dist/workflows.d.ts +3 -2
- package/dist/workflows.js +2 -151
- package/dist/workflows.js.map +1 -1
- package/package.json +20 -16
- package/src/adapters/claude/sdk.ts +4 -2
- package/src/adapters/claude/tool-bridge.ts +2 -1
- package/src/adapters/generation-dispatch.ts +9 -0
- package/src/adapters/openai/raw.ts +4 -0
- package/src/adapters/openai/sdk.ts +6 -1
- package/src/adapters/openai/tokens.ts +21 -7
- package/src/adapters/tool-bridge.ts +1 -1
- package/src/adapters/tools.ts +1 -1
- package/src/advanced/feedback.ts +1 -2
- package/src/advanced/generator.ts +4 -3
- package/src/boilerplate/free-design-prompt.ts +7 -0
- package/src/boilerplate/generate.ts +18 -5
- package/src/boilerplate/hard-sections.ts +8 -3
- package/src/boilerplate/react-hooks.ts +56 -0
- package/src/boilerplate/system-prompt.ts +12 -3
- package/src/check/run-tier0.ts +233 -8
- package/src/check/type-checker.ts +128 -16
- package/src/coding-agent/tools.ts +26 -3
- package/src/design-mode.ts +4 -9
- package/src/evaluation/axis-checks/checks/state-merge.ts +2 -4
- package/src/evaluation/axis-checks/checks/state-payload.ts +1 -28
- package/src/evaluation/axis-checks/checks/tint.ts +197 -0
- package/src/evaluation/axis-checks/helpers.ts +121 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/criteria/__fixtures__/edge-v2-draw2.json +4629 -0
- package/src/evaluation/criteria/bank.ts +1 -1
- package/src/evaluation/criteria/edge-instruments.ts +14 -0
- package/src/evaluation/criteria/edge-probe-v2.ts +352 -0
- package/src/evaluation/criteria/edge-v2.ts +242 -0
- package/src/evaluation/criteria/resolve.ts +24 -5
- package/src/evaluation/index.ts +3 -5
- package/src/evaluation/mcp-server.ts +0 -50
- package/src/evaluation/types-public.ts +6 -1
- package/src/evaluation/visual-evaluator.ts +161 -30
- package/src/harness/check/runtime-render/index.ts +28 -4
- package/src/harness/check/runtime-render/render-check.ts +20 -21
- package/src/harness/claude-agent-sdk-loader.ts +93 -0
- package/src/harness/claude-code-login-query.ts +1 -1
- package/src/harness/coding/generate-task-runner.ts +1 -0
- package/src/harness/coding/run-coding-turn.ts +3 -0
- package/src/harness/coding/run-eval-round.ts +9 -0
- package/src/harness/llm-router.ts +50 -2
- package/src/harness/runtime.ts +17 -0
- package/src/index.ts +9 -0
- package/src/providers/anthropic-tool-call.ts +263 -0
- package/src/providers/index.ts +13 -0
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/render-check.ts +4 -4
- package/src/validation/primitives.ts +2 -1
- package/src/evaluation/loop.ts +0 -218
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
import { createAgent } from './chunk-O2YC6XDC.js';
|
|
2
|
+
import './chunk-QLXXPGTV.js';
|
|
3
|
+
import './chunk-JDH5EGNZ.js';
|
|
4
|
+
import './chunk-OVLH5ICV.js';
|
|
5
|
+
import './chunk-7QE3AHLE.js';
|
|
6
|
+
import './chunk-5AGTY555.js';
|
|
7
|
+
import { buildStylingProfileJudgeBlock } from './chunk-NF6JUZWF.js';
|
|
8
|
+
import { LLM_EVAL_STATIC_CRITERIA, getCriterionById } from './chunk-PUH4CXGZ.js';
|
|
9
|
+
import { DEFAULT_DESIGN_MODE } from './chunk-3T2HMTON.js';
|
|
10
|
+
import './chunk-G3PMV62Z.js';
|
|
11
|
+
|
|
12
|
+
// src/evaluation/llm-evaluator.ts
|
|
13
|
+
function coerceStringArray(value) {
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return value.filter((v) => typeof v === "string" && v.length > 1);
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === "string") {
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(value);
|
|
20
|
+
if (Array.isArray(parsed)) {
|
|
21
|
+
return parsed.filter((v) => typeof v === "string" && v.length > 1);
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
if (value.length > 1) return [value];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
var FREE_STYLING_RULES = `## Styling Rules (important for evaluation)
|
|
30
|
+
- Color: every color must be a bare \`var(--ggui-color-*)\` token \u2014 a hardcoded #hex, rgb()/hsl() or CSS named color is a fail, and so is a literal fallback inside \`var(--ggui-*, \u2026)\`.
|
|
31
|
+
- Accent text (eyebrow, tagline, link, label) painted with a bare \`var(--ggui-color-primary-300\u2026600)\` is a readability fail \u2014 the accent ink is \`var(--ggui-color-link)\`; a tint as text on a primary fill and a deep stop (700\u2013900) on a light tint are fine
|
|
32
|
+
- Spacing, typography, radius, shadow geometry and layout values MAY be literals (\`padding: '20px'\`, \`fontSize: 18\`, \`borderRadius: 12\`) \u2014 NEVER flag them.
|
|
33
|
+
- Raw HTML elements with inline styles or \`<style>\` blocks are a first-class choice \u2014 never flag "not using the design system" or reward using it.
|
|
34
|
+
- When \`@ggui-ai/design\` primitives ARE used, their enum/scale props (\`variant="primary"\`, \`size="lg"\`, \`gap="md"\`, \`tone="muted"\`, \`surface="accent"\`) are valid.
|
|
35
|
+
- One \`export default function Component\` \u2014 helper components as named functions above it are fine`;
|
|
36
|
+
var CONSTRAINED_STYLING_RULES = `## Design System Rules (important for evaluation)
|
|
37
|
+
- Spacing props (\`gap\`, \`padding\`, \`margin\`) take a t-shirt-scale name: \`gap="md"\`, \`padding="lg"\` (\`none|xs|sm|md|lg|xl|2xl\`). **These ARE design tokens** \u2014 each resolves to a \`--ggui-spacing-*\` variable. A raw \`var(--ggui-spacing-*)\` string is an accepted escape hatch. NEVER flag a scale name as "hardcoded". Only a numeric prop (\`padding={24}\`) or a raw CSS length (\`gap="13px"\`) bypasses the scale and should be warned.
|
|
38
|
+
- All colors must use CSS variables: \`color="var(--ggui-color-onContainer)"\` \u2014 hardcoded #hex or rgb() is a fail
|
|
39
|
+
- Accent text (eyebrow, tagline, link, label) painted with a bare \`primary-300\u2026600\` stop is a readability fail \u2014 the accent ink is \`var(--ggui-color-link)\` (or \`tone="emphasized"\`); tints as text on a primary fill and deep stops on a light tint are fine
|
|
40
|
+
- Component props that take enum/scale values \u2014 \`variant="primary"\`, \`size="lg"\`, \`gap="md"\`, \`padding="lg"\`, \`shadow="md"\`, \`radius="lg"\`, \`tone="muted"\`, \`surface="accent"\` \u2014 are all design system tokens and are always valid
|
|
41
|
+
- One \`export default function Component\` \u2014 helper components as named functions above it are fine`;
|
|
42
|
+
var CONSTRAINED_VISUAL_REFERENCE = `**visual**: Design system consistency.
|
|
43
|
+
- Uses CSS variables (var(--ggui-*)) for colors, not hardcoded values
|
|
44
|
+
- Consistent use of spacing tokens
|
|
45
|
+
- Proper use of component variants (primary, outline, ghost)
|
|
46
|
+
- Shadow, border-radius from design system tokens`;
|
|
47
|
+
var FREE_VISUAL_REFERENCE = `**visual**: Visual composition.
|
|
48
|
+
- Clear hierarchy (one focal point, headings distinct from body), consistent spacing rhythm
|
|
49
|
+
- Coherent palette drawn from the theme color tokens
|
|
50
|
+
- Fit to the canvas: fluid width, nothing cramped or stretched
|
|
51
|
+
- No reward or penalty for using (or not using) the design-system primitives`;
|
|
52
|
+
var CONSTRAINED_RAW_CLICK_TARGET_GAP = `- A raw \`<div>\` / \`<span>\` used as a click target without \`as={Clickable}\`
|
|
53
|
+
(no role, no keyboard) \u2014 or any hand-rolled interactive element.`;
|
|
54
|
+
var FREE_RAW_CLICK_TARGET_GAP = `- A raw \`<div>\` / \`<span>\` used as a click target with no \`role="button"\`,
|
|
55
|
+
\`tabIndex\` and keyboard handler \u2014 a real \`<button>\` / \`<a href>\` is the
|
|
56
|
+
correct raw element and is never a gap.`;
|
|
57
|
+
function buildMotherPrompt(ctx) {
|
|
58
|
+
const parts = [];
|
|
59
|
+
const designMode = ctx.designMode ?? DEFAULT_DESIGN_MODE;
|
|
60
|
+
const free = designMode === "free";
|
|
61
|
+
parts.push(
|
|
62
|
+
free ? `You are evaluating a generated React component. It was composed freely \u2014 raw HTML elements, inline styles and <style> blocks are allowed and the ggui design package is optional \u2014 against a data contract and a theme-token color rule.
|
|
63
|
+
The code has already passed automated checks (compilation, security, imports, color tokens).
|
|
64
|
+
Your job is to evaluate ONE specific quality criterion per call.` : `You are evaluating a generated React component built with the ggui design system.
|
|
65
|
+
The code has already passed automated checks (compilation, security, imports, design tokens).
|
|
66
|
+
Your job is to evaluate ONE specific quality criterion per call.`
|
|
67
|
+
);
|
|
68
|
+
parts.push(`## About This Prompt
|
|
69
|
+
This system prompt has 2 sections:
|
|
70
|
+
1. **Context** \u2014 the original user request and data contract
|
|
71
|
+
2. **Criteria Reference** \u2014 definitions for all 7 evaluation criteria (your call will focus on one)
|
|
72
|
+
|
|
73
|
+
## Input Messages
|
|
74
|
+
You will receive ONE user message per call containing:
|
|
75
|
+
1. Which criterion to evaluate (e.g., "Evaluate FUNCTIONALITY")
|
|
76
|
+
2. The source code with line numbers
|
|
77
|
+
3. Example outputs showing the expected tool response format
|
|
78
|
+
|
|
79
|
+
Respond with a single tool call (evaluate_{criterion}). Report ALL issues you find for that criterion, not just the first one. Each issue should be an actionable fix instruction.
|
|
80
|
+
|
|
81
|
+
## Issue-array discipline (applies to EVERY criterion)
|
|
82
|
+
The "issues" array is for confirmed defects ONLY. Before adding an entry, it must be something you are CERTAIN is missing or broken. NEVER add:
|
|
83
|
+
- a self-negating note ("\u2026actually this is fine", "so no issue here", "no actual crash scenarios are present");
|
|
84
|
+
- a speculative / conditional note ("if the data is shaped X then\u2026", "this may break when\u2026");
|
|
85
|
+
- a "should be verified" / "double-check that\u2026" note.
|
|
86
|
+
If you are not certain something is broken, it is a PASS \u2014 omit it entirely. An entry you describe as acceptable still counts against the score, so describing a non-issue is worse than saying nothing.`);
|
|
87
|
+
parts.push(`## Original Request
|
|
88
|
+
${ctx.originalPrompt}`);
|
|
89
|
+
if (ctx.contract) {
|
|
90
|
+
parts.push(`## Data Contract (JSON Schema \u2192 TypeScript types)
|
|
91
|
+
|
|
92
|
+
The component's boilerplate was generated from this contract. All types are compiler-enforced:
|
|
93
|
+
- \`props\` \u2192 \`interface Props\` \u2014 every field must be rendered in the UI
|
|
94
|
+
- \`actionSpec\` (flat map keyed by action name) \u2192 \`useAction<Payload>(name)\` hooks \u2014 each must be wired to a UI element (button, form, etc.)
|
|
95
|
+
- \`actionSpec[name].nextStep\` (optional) \u2014 names an agent-side MCP tool the agent SHOULD invoke on its
|
|
96
|
+
next turn after the action fires. Surfaces as advisory event metadata; the component does NOT call
|
|
97
|
+
the named tool. Use it to judge whether button labels/icons match the underlying action's intent.
|
|
98
|
+
- \`streamSpec\` (flat map keyed by channel name) \u2192 \`useStream<T>(name)\` hooks \u2014 each must be consumed with null guard (\`.latest && ...\`)
|
|
99
|
+
- \`agentCapabilities.tools\` is a READ-ONLY catalog \u2014 the AGENT invokes these. The component never calls them.
|
|
100
|
+
References surface only via \`actionSpec[*].nextStep\` and \`streamSpec[*].source.tool\`. Do NOT flag
|
|
101
|
+
missing component-side calls for these.
|
|
102
|
+
- \`clientCapabilities.gadgets\` \u2192 a map keyed by npm PACKAGE name; each package maps export
|
|
103
|
+
names to a descriptor. Two kinds, both contract features that MUST be used:
|
|
104
|
+
\u2022 Built-in browser capabilities (\`useGeolocation\`, \`useCamera\`, \`useClipboardWrite\`,
|
|
105
|
+
\`useMicrophone\`, \`useFilePicker\`, \`useClipboardPaste\`, \`useNotifications\`) \u2014 imported
|
|
106
|
+
from \`@ggui-ai/gadgets\`.
|
|
107
|
+
\u2022 Registered third-party gadgets \u2014 hooks or components \u2014 imported from their OWN package
|
|
108
|
+
(e.g. \`import { useChartTheme } from '@ggui-samples/gadget-chart'\`). The boilerplate
|
|
109
|
+
emits that import. Any export listed under \`clientCapabilities.gadgets\` IS part of the
|
|
110
|
+
contract \u2014 NEVER flag it as "not part of the contract".
|
|
111
|
+
|
|
112
|
+
\`\`\`json
|
|
113
|
+
${JSON.stringify(ctx.contract).slice(0, 6e3)}
|
|
114
|
+
\`\`\``);
|
|
115
|
+
}
|
|
116
|
+
if (ctx.designSystemSummary) {
|
|
117
|
+
parts.push(`## Design System
|
|
118
|
+
${ctx.designSystemSummary}`);
|
|
119
|
+
}
|
|
120
|
+
parts.push(`${free ? FREE_STYLING_RULES : CONSTRAINED_STYLING_RULES}
|
|
121
|
+
|
|
122
|
+
## Evaluation Criteria Reference
|
|
123
|
+
|
|
124
|
+
### Tier 1 \u2014 Critical (blocks delivery)
|
|
125
|
+
|
|
126
|
+
**functionality**: Does the component implement ALL features in the original request?
|
|
127
|
+
- Check every noun/verb in the request against the rendered output
|
|
128
|
+
- Missing feature = fail, even if other features work perfectly
|
|
129
|
+
- Partial implementation = fail (e.g., search UI exists but doesn't filter)
|
|
130
|
+
|
|
131
|
+
**crash**: Are there runtime crash scenarios?
|
|
132
|
+
- Uninitialized state used in render (e.g., items.map when items could be undefined)
|
|
133
|
+
- Missing event handlers referenced in JSX
|
|
134
|
+
- Destructuring undefined objects
|
|
135
|
+
- Array access without bounds checking
|
|
136
|
+
- Missing null/undefined guards before .map(), .filter(), .length
|
|
137
|
+
|
|
138
|
+
### Tier 2 \u2014 Quality (fail if severe, warn if improvement)
|
|
139
|
+
|
|
140
|
+
**interactivity**: Does it have interactive elements appropriate for its purpose?
|
|
141
|
+
- Forms should have submit buttons
|
|
142
|
+
- Lists should be navigable
|
|
143
|
+
- Editable fields should have save/cancel
|
|
144
|
+
- Interactive components should have hover/focus feedback
|
|
145
|
+
|
|
146
|
+
**accessibility**: Semantic HTML, labels, alt text, keyboard navigation, ARIA state.
|
|
147
|
+
- Form inputs must have associated labels (label prop or aria-label)
|
|
148
|
+
- Images need alt text
|
|
149
|
+
- Interactive elements need keyboard support
|
|
150
|
+
- Interactive controls expose their state (aria-checked / aria-selected / aria-expanded / aria-pressed), never styling alone
|
|
151
|
+
- Proper heading hierarchy (h1 > h2 > h3)
|
|
152
|
+
|
|
153
|
+
**layout**: Spacing, alignment, visual grouping.
|
|
154
|
+
- Consistent spacing between sections
|
|
155
|
+
- Related items grouped visually
|
|
156
|
+
- No overlapping or cramped elements
|
|
157
|
+
- Proper use of containers and cards for grouping
|
|
158
|
+
|
|
159
|
+
**loading**: Loading/error/empty states for dynamic data.
|
|
160
|
+
- Components receiving async data should show loading state
|
|
161
|
+
- Error boundaries or error messages for failure cases
|
|
162
|
+
- Empty states ("No items found") when data arrays could be empty
|
|
163
|
+
|
|
164
|
+
${free ? FREE_VISUAL_REFERENCE : CONSTRAINED_VISUAL_REFERENCE}
|
|
165
|
+
|
|
166
|
+
## Primitive Accessibility (built-in \u2014 do NOT flag as missing)
|
|
167
|
+
|
|
168
|
+
ggui design-system primitives ship their own ARIA. The code you are
|
|
169
|
+
reading imports them from \`@ggui-ai/design\`; the source does NOT show
|
|
170
|
+
their internal markup. NEVER report a primitive below as missing a
|
|
171
|
+
role, aria-*, label, or keyboard support \u2014 it is already there:
|
|
172
|
+
|
|
173
|
+
- \`Input\` / \`Select\` / \`TextArea\` \u2014 render a real \`<label htmlFor>\` from
|
|
174
|
+
the \`label\` prop, plus \`aria-invalid\` + \`aria-describedby\` + an error
|
|
175
|
+
\`role="alert"\`. A primitive that has a \`label\` prop IS labelled.
|
|
176
|
+
- \`RadioGroup\` \u2014 \`role="radiogroup"\` + \`aria-labelledby\` from its \`label\`.
|
|
177
|
+
- \`Checkbox\` \u2014 wrapped in its own \`<label>\`.
|
|
178
|
+
- \`Toggle\` \u2014 \`role="switch"\` + \`aria-checked\` + \`aria-label\`.
|
|
179
|
+
- \`Progress\` \u2014 \`role="progressbar"\` + \`aria-valuenow/min/max\` + \`aria-label\`.
|
|
180
|
+
- \`Slider\` \u2014 full \`aria-valuenow/min/max\` slider semantics.
|
|
181
|
+
- \`Spinner\` \u2014 \`role="status"\` + \`aria-label="Loading"\`.
|
|
182
|
+
- \`Skeleton\` \u2014 \`aria-hidden\` (correctly decorative).
|
|
183
|
+
- \`Tabs\` \u2014 full \`role="tablist"/"tab"/"tabpanel"\` + \`aria-selected/controls\`.
|
|
184
|
+
- \`Stepper\` \u2014 \`<nav aria-label="Progress">\` + \`aria-current="step"\` on the
|
|
185
|
+
active step; steps become real buttons when \`onStepClick\` is passed. It is
|
|
186
|
+
display-only by design \u2014 the component owning the step index in its own
|
|
187
|
+
\`useState\` is CORRECT, never a gap.
|
|
188
|
+
- \`Accordion\` \u2014 \`aria-expanded\` + \`aria-controls\` + \`role="region"\`.
|
|
189
|
+
- \`Alert\` \u2014 \`role="alert"\`; \`Toast\` \u2014 \`role="alert"\` + \`aria-live\`.
|
|
190
|
+
- \`Tooltip\` \u2014 \`role="tooltip"\`.
|
|
191
|
+
- \`Clickable\` (trait, applied via \`as={Clickable}\`) \u2014 \`role="button"\` +
|
|
192
|
+
\`tabIndex={0}\` + Enter/Space keyboard activation.
|
|
193
|
+
- \`Icon\` \u2014 decorative by default (\`aria-hidden\`); exposes \`role="img"\` +
|
|
194
|
+
the label ONLY when an \`aria-label\` prop is passed. A bare \`<Icon>\` next
|
|
195
|
+
to text is CORRECTLY silent \u2014 never ask for \`aria-hidden\`, it is there.
|
|
196
|
+
|
|
197
|
+
REAL accessibility gaps worth flagging:
|
|
198
|
+
${free ? FREE_RAW_CLICK_TARGET_GAP : CONSTRAINED_RAW_CLICK_TARGET_GAP}
|
|
199
|
+
- A hand-rolled control whose state (checked / selected / expanded /
|
|
200
|
+
pressed) is conveyed ONLY by styling \u2014 e.g. a styled-div "checkbox"
|
|
201
|
+
with a check icon + strikethrough label but no \`role="checkbox"\` /
|
|
202
|
+
\`aria-checked\`. Note \`as={Clickable}\` adds button + keyboard
|
|
203
|
+
semantics but NOT state \u2014 \`aria-checked\` / \`aria-pressed\` /
|
|
204
|
+
\`aria-selected\` / \`aria-expanded\` must be passed explicitly.
|
|
205
|
+
- An \`<img>\` / \`<Image>\` with no alt text.
|
|
206
|
+
- \`<Input>\` / \`<Select>\` / \`<TextArea>\` used with NO \`label\` prop.
|
|
207
|
+
- An icon-only \`<Button>\` (no text child) with no \`aria-label\`.
|
|
208
|
+
- Live or streaming data (stream \`.latest\`, counters, real-time clocks)
|
|
209
|
+
rendered in a plain element with no \`aria-live\` / \`role="status"\`.
|
|
210
|
+
- Inverted or skipped heading levels (an \`<Heading level={3}>\` rendered
|
|
211
|
+
before the page's \`<Heading level={1}>\`).`);
|
|
212
|
+
const profileBlock = buildStylingProfileJudgeBlock(ctx.profile);
|
|
213
|
+
if (profileBlock.length > 0) parts.push(profileBlock);
|
|
214
|
+
return parts.join("\n\n");
|
|
215
|
+
}
|
|
216
|
+
function makeTier1Tool(criterion) {
|
|
217
|
+
return {
|
|
218
|
+
name: `evaluate_${criterion}`,
|
|
219
|
+
description: `Submit evaluation result for ${criterion}`,
|
|
220
|
+
parameters: {
|
|
221
|
+
type: "object",
|
|
222
|
+
required: ["pass"],
|
|
223
|
+
properties: {
|
|
224
|
+
pass: { type: "boolean" },
|
|
225
|
+
issues: {
|
|
226
|
+
type: "array",
|
|
227
|
+
items: { type: "string" },
|
|
228
|
+
description: "List of specific issues found"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function makeTier2Tool(criterion) {
|
|
235
|
+
return {
|
|
236
|
+
name: `evaluate_${criterion}`,
|
|
237
|
+
description: `Submit evaluation result for ${criterion}`,
|
|
238
|
+
parameters: {
|
|
239
|
+
type: "object",
|
|
240
|
+
required: ["result"],
|
|
241
|
+
properties: {
|
|
242
|
+
result: {
|
|
243
|
+
type: "string",
|
|
244
|
+
enum: ["fail", "warn", "pass"]
|
|
245
|
+
},
|
|
246
|
+
issues: {
|
|
247
|
+
type: "array",
|
|
248
|
+
items: { type: "string" },
|
|
249
|
+
description: "List of specific issues found"
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function getCriterionPrompt(criterion, numberedSource, designMode) {
|
|
256
|
+
const sourceBlock = `
|
|
257
|
+
Source code (with line numbers):
|
|
258
|
+
\`\`\`tsx
|
|
259
|
+
${numberedSource}
|
|
260
|
+
\`\`\``;
|
|
261
|
+
const criterionDef = getCriterionById(criterion, designMode);
|
|
262
|
+
if (criterionDef && criterionDef.tier > 0) {
|
|
263
|
+
return `${criterionDef.evalInstruction}
|
|
264
|
+
${sourceBlock}
|
|
265
|
+
|
|
266
|
+
Return your evaluation using the evaluate_${criterion} tool.`;
|
|
267
|
+
}
|
|
268
|
+
switch (criterion) {
|
|
269
|
+
case "functionality":
|
|
270
|
+
return `Evaluate FUNCTIONALITY: Does this component implement ALL features from the request AND the data contract?
|
|
271
|
+
|
|
272
|
+
Check against BOTH sources:
|
|
273
|
+
1. **Original request** \u2014 each feature must be coded AND rendered in JSX
|
|
274
|
+
2. **Data contract** (if present in the context above) \u2014 verify:
|
|
275
|
+
- ALL Props fields are rendered somewhere in the UI (not just accepted as params)
|
|
276
|
+
- ALL useAction hooks are wired to clickable UI elements (buttons, forms, cards)
|
|
277
|
+
- ALL useStream hooks have their \`.latest\` data rendered (with null guard)
|
|
278
|
+
- ALL clientCapabilities hooks (useGeolocation / useCamera / etc.) are called in the component body and their values either render directly or thread into a context / action payload
|
|
279
|
+
|
|
280
|
+
A contract hook that is declared but never used in JSX is a MISSING feature.
|
|
281
|
+
|
|
282
|
+
**CRITICAL RULE:** The "issues" array must ONLY contain MISSING or BROKEN features. Do NOT list features that ARE implemented.
|
|
283
|
+
|
|
284
|
+
Design token usage (hardcoded colors, numeric spacing) is NOT a functionality issue \u2014 ignore it here.
|
|
285
|
+
${sourceBlock}
|
|
286
|
+
|
|
287
|
+
Return your evaluation using the evaluate_functionality tool.
|
|
288
|
+
|
|
289
|
+
Example outputs:
|
|
290
|
+
- All features present: {"pass": true}
|
|
291
|
+
- Missing features: {"pass": false, "issues": ["Add Task form is not rendered \u2014 only state logic exists", "useAction('refresh') is declared but never wired to any button"]}
|
|
292
|
+
|
|
293
|
+
Only missing or broken features go in the issues array. Nothing else.`;
|
|
294
|
+
case "crash":
|
|
295
|
+
return `Evaluate CRASH SAFETY: Are there ACTUAL runtime crash scenarios in this component?
|
|
296
|
+
|
|
297
|
+
**CRITICAL RULE:** The "issues" array must ONLY contain code that WILL crash. Do NOT include safe code in the issues array \u2014 not even to explain why it's safe. If you checked a line and it has a guard, do not mention it at all.
|
|
298
|
+
|
|
299
|
+
**These WILL crash (include in issues):**
|
|
300
|
+
- .map() / .filter() / .length on a variable that is never initialized
|
|
301
|
+
- Accessing a property of a value that could be undefined without a guard
|
|
302
|
+
- Calling a function that is never defined
|
|
303
|
+
- useStream().latest.field WITHOUT null guard \u2014 .latest is T | null
|
|
304
|
+
- Optional Props field (has \`?\` in interface) accessed as props.field.x without guard \u2014 WILL crash if undefined
|
|
305
|
+
- Array item field that could be undefined: items.map(item => item.priority.toUpperCase()) when priority is optional
|
|
306
|
+
|
|
307
|
+
**These are SAFE (do NOT include in issues):**
|
|
308
|
+
- Optional chaining: props.items?.map() \u2014 SAFE, skip it
|
|
309
|
+
- Fallback values: items || [] \u2014 SAFE, skip it
|
|
310
|
+
- Default parameters: (items = []) \u2014 SAFE, skip it
|
|
311
|
+
- useState initializer: useState([]) \u2014 SAFE, skip it
|
|
312
|
+
- Null check: items && items.map() \u2014 SAFE, skip it
|
|
313
|
+
- stream.latest && stream.latest.field \u2014 SAFE, guarded
|
|
314
|
+
- stream.all.map(...) \u2014 SAFE, .all is always an array (never null)
|
|
315
|
+
- stream.all.length \u2014 SAFE, always a number
|
|
316
|
+
- toolResult.data && toolResult.data.field \u2014 SAFE, guarded
|
|
317
|
+
|
|
318
|
+
If everything is safe, return {"pass": true} with no issues array.
|
|
319
|
+
${sourceBlock}
|
|
320
|
+
|
|
321
|
+
Return your evaluation using the evaluate_crash tool.
|
|
322
|
+
|
|
323
|
+
Example outputs:
|
|
324
|
+
- Everything safe: {"pass": true}
|
|
325
|
+
- Has crash: {"pass": false, "issues": ["Line 41: selectedItem.name \u2014 selectedItem comes from .find() which returns undefined when no match, no guard"]}
|
|
326
|
+
|
|
327
|
+
Only unguarded crash risks go in the issues array. Nothing else.`;
|
|
328
|
+
case "interactivity":
|
|
329
|
+
return `Evaluate INTERACTIVITY: Does this component have sufficient interactive elements for its purpose?
|
|
330
|
+
|
|
331
|
+
Consider: forms need submit buttons, lists need selection/navigation, editable content needs save/cancel, interactive elements need hover/focus feedback.
|
|
332
|
+
|
|
333
|
+
**Contract actions** (if present): every useAction hook from the data contract should be triggered by a visible, clickable UI element. A declared action with no triggering button/link/form is a missing interaction.
|
|
334
|
+
|
|
335
|
+
**Only list MISSING interactive elements in the issues array.** Do not list interactions that are present and working.
|
|
336
|
+
${sourceBlock}
|
|
337
|
+
|
|
338
|
+
Return your evaluation using the evaluate_interactivity tool.
|
|
339
|
+
|
|
340
|
+
Example outputs:
|
|
341
|
+
- Sufficient: {"result": "pass"}
|
|
342
|
+
- Minor gap: {"result": "warn", "issues": ["No hover feedback on list items \u2014 users can't tell items are clickable"]}
|
|
343
|
+
- Severe gap: {"result": "fail", "issues": ["Form has no submit button \u2014 users cannot complete the workflow"]}
|
|
344
|
+
|
|
345
|
+
Use 'fail' only for issues that block the component's core purpose.`;
|
|
346
|
+
case "accessibility":
|
|
347
|
+
return `Evaluate ACCESSIBILITY: Check for missing labels, alt text, semantic HTML, keyboard navigation.
|
|
348
|
+
|
|
349
|
+
Look for: form inputs without labels, images without alt text, non-semantic elements used for interactive purposes, missing aria attributes, heading hierarchy issues.
|
|
350
|
+
|
|
351
|
+
**Only list MISSING accessibility features in the issues array.** Do not list things that are already accessible.
|
|
352
|
+
${sourceBlock}
|
|
353
|
+
|
|
354
|
+
Return your evaluation using the evaluate_accessibility tool.
|
|
355
|
+
|
|
356
|
+
Example outputs:
|
|
357
|
+
- Clean: {"result": "pass"}
|
|
358
|
+
- Minor issues: {"result": "warn", "issues": ["Email input missing label prop", "Task edit uses double-click with no keyboard alternative"]}
|
|
359
|
+
- Severe: {"result": "fail", "issues": ["No form inputs have labels \u2014 entire form is inaccessible to screen readers"]}
|
|
360
|
+
|
|
361
|
+
Use 'fail' only if the issue blocks delivery. Use 'warn' for improvements.`;
|
|
362
|
+
case "layout":
|
|
363
|
+
return `Evaluate LAYOUT: Check spacing, alignment, visual grouping, and overall composition.
|
|
364
|
+
|
|
365
|
+
Look for: inconsistent spacing, cramped elements, missing visual grouping (cards/sections), overlapping concerns in single containers, poor responsive patterns.
|
|
366
|
+
|
|
367
|
+
**Only list ACTUAL layout problems in the issues array.** Do not list layout aspects that are working correctly.
|
|
368
|
+
${sourceBlock}
|
|
369
|
+
|
|
370
|
+
Return your evaluation using the evaluate_layout tool.
|
|
371
|
+
|
|
372
|
+
Example outputs:
|
|
373
|
+
- Clean: {"result": "pass"}
|
|
374
|
+
- Minor issues: {"result": "warn", "issues": ["Header and content area have no visual separation", "Button group lacks consistent spacing"]}
|
|
375
|
+
- Severe: {"result": "fail", "issues": ["All content rendered in a flat list with no visual structure \u2014 needs cards or sections"]}
|
|
376
|
+
|
|
377
|
+
Use 'fail' only for layouts that are fundamentally broken.`;
|
|
378
|
+
case "loading":
|
|
379
|
+
return `Evaluate LOADING/EMPTY/ERROR STATES: Does the component handle async data, empty collections, and error cases?
|
|
380
|
+
|
|
381
|
+
Look for: arrays that could be empty but have no empty state message, missing error handling for operations that can fail.
|
|
382
|
+
|
|
383
|
+
**Contract-specific checks** (if present):
|
|
384
|
+
- useStream hooks deliver data over time \u2014 consider showing a "waiting for data" state before .latest arrives
|
|
385
|
+
- clientCapabilities hooks (e.g. useGeolocation) may return permission denials or undefined values \u2014 defensive null guards expected before threading into JSX
|
|
386
|
+
|
|
387
|
+
**Only list MISSING states in the issues array.** Do not list states that are already handled. Components that receive all data via props (no streams, no async capabilities) do NOT need loading states \u2014 return pass.
|
|
388
|
+
${sourceBlock}
|
|
389
|
+
|
|
390
|
+
Return your evaluation using the evaluate_loading tool.
|
|
391
|
+
|
|
392
|
+
Example outputs:
|
|
393
|
+
- Not applicable (props-driven): {"result": "pass"}
|
|
394
|
+
- Missing states: {"result": "warn", "issues": ["No empty state when items array is empty \u2014 should show 'No items found'"]}
|
|
395
|
+
- Severe: {"result": "fail", "issues": ["Component shows blank screen when data hasn't loaded yet \u2014 needs loading spinner"]}`;
|
|
396
|
+
case "visual":
|
|
397
|
+
return `Evaluate VISUAL CONSISTENCY: Is the component using the design system correctly?
|
|
398
|
+
|
|
399
|
+
Look for: hardcoded colors instead of CSS variables, numeric/raw-CSS spacing props that bypass the t-shirt scale, style objects that bypass the design system.
|
|
400
|
+
|
|
401
|
+
**Only list ACTUAL design system violations in the issues array.** Do not list correct token usage. A t-shirt-scale spacing name (\`gap="md"\`, \`padding="lg"\`) IS correct token usage \u2014 never flag it as hardcoded; only numeric (\`padding={16}\`) or raw-CSS-length (\`gap="13px"\`) spacing is a violation. Note: intentional custom colors (e.g., status indicators, category colors) are acceptable when no semantic token fits.
|
|
402
|
+
${sourceBlock}
|
|
403
|
+
|
|
404
|
+
Return your evaluation using the evaluate_visual tool.
|
|
405
|
+
|
|
406
|
+
Example outputs:
|
|
407
|
+
- Clean: {"result": "pass"}
|
|
408
|
+
- Minor issues: {"result": "warn", "issues": ["Line 45: hardcoded '#f3f4f6' should use var(--ggui-color-sunken)", "Line 80: numeric padding={16} should use a scale name like padding='md'"]}
|
|
409
|
+
- Severe: {"result": "fail", "issues": ["Component uses entirely custom styling with no design system tokens"]}
|
|
410
|
+
|
|
411
|
+
Use 'fail' only for pervasive design system violations. Use 'warn' for isolated cases.`;
|
|
412
|
+
default:
|
|
413
|
+
return `Evaluate ${criterion.toUpperCase()}.${sourceBlock}
|
|
414
|
+
|
|
415
|
+
Return your evaluation using the evaluate_${criterion} tool.`;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function getDefaultEvalModel(provider) {
|
|
419
|
+
switch (provider) {
|
|
420
|
+
case "claude":
|
|
421
|
+
return "claude-haiku-5-5";
|
|
422
|
+
case "openai":
|
|
423
|
+
return "gpt-5.6-luna";
|
|
424
|
+
case "google":
|
|
425
|
+
return "gemini-3.5-flash-lite";
|
|
426
|
+
default:
|
|
427
|
+
return "claude-haiku-5-5";
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function callTokens(response) {
|
|
431
|
+
return {
|
|
432
|
+
in: response.inputTokens,
|
|
433
|
+
out: response.outputTokens,
|
|
434
|
+
...response.cacheReadTokens !== void 0 ? { cacheRead: response.cacheReadTokens } : {},
|
|
435
|
+
...response.cacheCreationTokens !== void 0 ? { cacheCreation: response.cacheCreationTokens } : {}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
var TIER_1_CRITERIA = LLM_EVAL_STATIC_CRITERIA.filter((c) => c.tier === 1).map((c) => c.criterion);
|
|
439
|
+
var TIER_2_CRITERIA = LLM_EVAL_STATIC_CRITERIA.filter((c) => c.tier === 2).map((c) => c.criterion);
|
|
440
|
+
async function evalCriterion(criterion, tier, motherPrompt, numberedSource, agent, model, designMode) {
|
|
441
|
+
const start = Date.now();
|
|
442
|
+
const tool = tier === 1 ? makeTier1Tool(criterion) : makeTier2Tool(criterion);
|
|
443
|
+
const userPrompt = getCriterionPrompt(criterion, numberedSource, designMode);
|
|
444
|
+
try {
|
|
445
|
+
const response = await agent.callTools(
|
|
446
|
+
model,
|
|
447
|
+
motherPrompt,
|
|
448
|
+
userPrompt,
|
|
449
|
+
[tool],
|
|
450
|
+
"required"
|
|
451
|
+
);
|
|
452
|
+
const call = response.toolCalls[0];
|
|
453
|
+
const durationMs = Date.now() - start;
|
|
454
|
+
if (!call) {
|
|
455
|
+
console.warn(`[eval] ${criterion}: no tool call returned`);
|
|
456
|
+
return {
|
|
457
|
+
criterion,
|
|
458
|
+
tier,
|
|
459
|
+
issues: [],
|
|
460
|
+
pass: true,
|
|
461
|
+
coverage: "skipped",
|
|
462
|
+
skipReason: "no tool call returned",
|
|
463
|
+
tokens: callTokens(response),
|
|
464
|
+
durationMs
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const raw = call.input;
|
|
468
|
+
const issues = [];
|
|
469
|
+
let pass;
|
|
470
|
+
if (tier === 1) {
|
|
471
|
+
pass = raw.pass === true;
|
|
472
|
+
const rawIssues = coerceStringArray(raw.issues);
|
|
473
|
+
for (const desc of rawIssues) {
|
|
474
|
+
issues.push({
|
|
475
|
+
tier: 1,
|
|
476
|
+
result: "fail",
|
|
477
|
+
category: criterion,
|
|
478
|
+
severity: "critical",
|
|
479
|
+
description: desc,
|
|
480
|
+
fix: desc
|
|
481
|
+
// The issue description often includes the fix for tier 1
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
if (!pass && issues.length === 0) {
|
|
485
|
+
issues.push({
|
|
486
|
+
tier: 1,
|
|
487
|
+
result: "fail",
|
|
488
|
+
category: criterion,
|
|
489
|
+
severity: "critical",
|
|
490
|
+
description: `${criterion} check failed`,
|
|
491
|
+
fix: `Fix ${criterion} issues`
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
} else {
|
|
495
|
+
const result = raw.result ?? "pass";
|
|
496
|
+
pass = result === "pass";
|
|
497
|
+
const rawIssues = coerceStringArray(raw.issues);
|
|
498
|
+
const outcome = result === "fail" ? "fail" : "warn";
|
|
499
|
+
for (const desc of rawIssues) {
|
|
500
|
+
issues.push({
|
|
501
|
+
tier: 2,
|
|
502
|
+
result: outcome,
|
|
503
|
+
category: criterion,
|
|
504
|
+
severity: result === "fail" ? "critical" : "major",
|
|
505
|
+
description: desc,
|
|
506
|
+
fix: desc
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
criterion,
|
|
512
|
+
tier,
|
|
513
|
+
issues,
|
|
514
|
+
pass,
|
|
515
|
+
coverage: "ran",
|
|
516
|
+
tokens: callTokens(response),
|
|
517
|
+
durationMs
|
|
518
|
+
};
|
|
519
|
+
} catch (e) {
|
|
520
|
+
const durationMs = Date.now() - start;
|
|
521
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
522
|
+
console.error(`[eval] ${criterion}: error \u2014 ${message}`);
|
|
523
|
+
return {
|
|
524
|
+
criterion,
|
|
525
|
+
tier,
|
|
526
|
+
issues: [],
|
|
527
|
+
pass: true,
|
|
528
|
+
coverage: "skipped",
|
|
529
|
+
skipReason: message,
|
|
530
|
+
tokens: { in: 0, out: 0 },
|
|
531
|
+
durationMs
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async function preWarmEval(context, _config) {
|
|
536
|
+
const motherPrompt = buildMotherPrompt({
|
|
537
|
+
originalPrompt: context.originalPrompt,
|
|
538
|
+
contract: context.contract,
|
|
539
|
+
designSystemSummary: context.designContext,
|
|
540
|
+
designMode: context.designMode,
|
|
541
|
+
profile: context.profile
|
|
542
|
+
});
|
|
543
|
+
return { motherPrompt, dynamicCriteria: [] };
|
|
544
|
+
}
|
|
545
|
+
var TIER_1_CATEGORIES = /* @__PURE__ */ new Set(["functionality", "crash"]);
|
|
546
|
+
var TIER_2_CATEGORIES = /* @__PURE__ */ new Set([
|
|
547
|
+
"interactivity",
|
|
548
|
+
"accessibility",
|
|
549
|
+
"layout",
|
|
550
|
+
"loading",
|
|
551
|
+
"visual"
|
|
552
|
+
]);
|
|
553
|
+
var VALID_CATEGORIES = /* @__PURE__ */ new Set([
|
|
554
|
+
...TIER_1_CATEGORIES,
|
|
555
|
+
...TIER_2_CATEGORIES
|
|
556
|
+
]);
|
|
557
|
+
function parseEvalResponse(raw) {
|
|
558
|
+
const rawIssues = raw.issues ?? [];
|
|
559
|
+
const rawPass = raw.pass ?? [];
|
|
560
|
+
const issues = rawIssues.filter((i) => {
|
|
561
|
+
const cat = i.category;
|
|
562
|
+
return VALID_CATEGORIES.has(cat);
|
|
563
|
+
}).map((i) => {
|
|
564
|
+
const category = i.category;
|
|
565
|
+
const isTier1 = TIER_1_CATEGORIES.has(category);
|
|
566
|
+
const tier = isTier1 ? 1 : 2;
|
|
567
|
+
const result = isTier1 ? "fail" : i.result === "fail" ? "fail" : "warn";
|
|
568
|
+
return {
|
|
569
|
+
tier,
|
|
570
|
+
result,
|
|
571
|
+
category,
|
|
572
|
+
severity: i.severity || (isTier1 ? "critical" : "major"),
|
|
573
|
+
description: i.description || "",
|
|
574
|
+
fix: i.fix || ""
|
|
575
|
+
};
|
|
576
|
+
});
|
|
577
|
+
return {
|
|
578
|
+
issues,
|
|
579
|
+
pass: rawPass
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
async function runLLMEvaluation(context, config, preWarmedContext) {
|
|
583
|
+
const providerName = config.provider;
|
|
584
|
+
const model = config.model || getDefaultEvalModel(providerName);
|
|
585
|
+
const routerProvider = providerName === "claude" ? "anthropic" : providerName;
|
|
586
|
+
const designMode = context.designMode ?? DEFAULT_DESIGN_MODE;
|
|
587
|
+
const motherPrompt = preWarmedContext?.motherPrompt ?? buildMotherPrompt({
|
|
588
|
+
originalPrompt: context.originalPrompt,
|
|
589
|
+
contract: context.contract,
|
|
590
|
+
designSystemSummary: context.designContext,
|
|
591
|
+
designMode,
|
|
592
|
+
profile: context.profile
|
|
593
|
+
});
|
|
594
|
+
const numberedSource = context.sourceCode.split("\n").map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n");
|
|
595
|
+
const agent = createAgent({
|
|
596
|
+
provider: routerProvider,
|
|
597
|
+
model,
|
|
598
|
+
routeOverride: config.routeOverride,
|
|
599
|
+
onRetry: config.onRetry
|
|
600
|
+
});
|
|
601
|
+
const evalStart = Date.now();
|
|
602
|
+
const totalCriteriaCount = TIER_1_CRITERIA.length + TIER_2_CRITERIA.length;
|
|
603
|
+
const results = await Promise.all([
|
|
604
|
+
...TIER_1_CRITERIA.map((c) => evalCriterion(c, 1, motherPrompt, numberedSource, agent, model, designMode)),
|
|
605
|
+
...TIER_2_CRITERIA.map((c) => evalCriterion(c, 2, motherPrompt, numberedSource, agent, model, designMode))
|
|
606
|
+
]);
|
|
607
|
+
const allIssues = [];
|
|
608
|
+
const passCategories = [];
|
|
609
|
+
let totalIn = 0;
|
|
610
|
+
let totalOut = 0;
|
|
611
|
+
let cacheRead;
|
|
612
|
+
let cacheCreation;
|
|
613
|
+
for (const r of results) {
|
|
614
|
+
allIssues.push(...r.issues);
|
|
615
|
+
if (r.pass) {
|
|
616
|
+
passCategories.push(r.criterion);
|
|
617
|
+
}
|
|
618
|
+
totalIn += r.tokens.in;
|
|
619
|
+
totalOut += r.tokens.out;
|
|
620
|
+
if (r.tokens.cacheRead !== void 0) cacheRead = (cacheRead ?? 0) + r.tokens.cacheRead;
|
|
621
|
+
if (r.tokens.cacheCreation !== void 0) cacheCreation = (cacheCreation ?? 0) + r.tokens.cacheCreation;
|
|
622
|
+
}
|
|
623
|
+
const wallMs = Date.now() - evalStart;
|
|
624
|
+
const criteriaCoverage = results.map((r) => ({
|
|
625
|
+
criterion: r.criterion,
|
|
626
|
+
tier: r.tier,
|
|
627
|
+
status: r.coverage,
|
|
628
|
+
...r.skipReason !== void 0 ? { reason: r.skipReason } : {}
|
|
629
|
+
}));
|
|
630
|
+
const skippedCount = criteriaCoverage.filter((c) => c.status === "skipped").length;
|
|
631
|
+
for (const r of results) {
|
|
632
|
+
const issueCount = r.issues.length;
|
|
633
|
+
const status = r.coverage === "skipped" ? "SKIPPED" : r.pass ? "PASS" : r.tier === 1 ? "FAIL" : r.issues.some((i) => i.result === "fail") ? "FAIL" : "WARN";
|
|
634
|
+
const issueLabel = issueCount > 0 ? ` (${issueCount} issue${issueCount > 1 ? "s" : ""})` : "";
|
|
635
|
+
console.log(`[eval] ${r.criterion}: ${status}${issueLabel} ${(r.durationMs / 1e3).toFixed(1)}s`);
|
|
636
|
+
}
|
|
637
|
+
console.log(
|
|
638
|
+
`[eval] total: ${(wallMs / 1e3).toFixed(1)}s wall (${totalCriteriaCount} parallel) | in=${totalIn} out=${totalOut} cacheRead=${cacheRead ?? "unreported"} cacheWrite=${cacheCreation ?? "unreported"} | issues=${allIssues.length} pass=${passCategories.length}` + (skippedCount > 0 ? ` skipped=${skippedCount}` : "")
|
|
639
|
+
);
|
|
640
|
+
return {
|
|
641
|
+
issues: allIssues,
|
|
642
|
+
pass: passCategories,
|
|
643
|
+
criteriaCoverage,
|
|
644
|
+
inputTokens: totalIn,
|
|
645
|
+
outputTokens: totalOut,
|
|
646
|
+
...cacheRead !== void 0 ? { cacheReadTokens: cacheRead } : {},
|
|
647
|
+
...cacheCreation !== void 0 ? { cacheCreationTokens: cacheCreation } : {}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export { buildMotherPrompt, parseEvalResponse, preWarmEval, runLLMEvaluation };
|
|
652
|
+
//# sourceMappingURL=llm-evaluator-PN3WFCS4.js.map
|
|
653
|
+
//# sourceMappingURL=llm-evaluator-PN3WFCS4.js.map
|