@ggui-ai/ui-gen 0.16.0 → 0.17.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.
Files changed (101) hide show
  1. package/dist/adapters/generation-dispatch.d.ts +6 -3
  2. package/dist/adapters/generation-dispatch.js +4944 -4239
  3. package/dist/adapters/generation-dispatch.js.map +1 -1
  4. package/dist/adapters/index.js +8 -5
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/advanced/index.d.ts +12 -8
  7. package/dist/advanced/index.js +4462 -3726
  8. package/dist/advanced/index.js.map +1 -1
  9. package/dist/blueprint-validator.js +8 -12
  10. package/dist/blueprint-validator.js.map +1 -1
  11. package/dist/boilerplate.d.ts +5 -2
  12. package/dist/boilerplate.js +95 -12
  13. package/dist/boilerplate.js.map +1 -1
  14. package/dist/check/index.d.ts +2 -2
  15. package/dist/check/index.js +2 -0
  16. package/dist/check/index.js.map +1 -1
  17. package/dist/compose.js +1 -1
  18. package/dist/compose.js.map +1 -1
  19. package/dist/{create-ui-generator-CCIeQaEe.d.ts → create-ui-generator-C8Ki9Wz1.d.ts} +18 -2
  20. package/dist/{design-mode-BHEE7flj.d.ts → design-mode-DLufhrNp.d.ts} +0 -6
  21. package/dist/{dispatch-BA5dJcg-.d.ts → dispatch-D2htzi9h.d.ts} +2 -2
  22. package/dist/evaluation/axis-checks/index.d.ts +4 -4
  23. package/dist/evaluation/axis-checks/index.js +297 -13
  24. package/dist/evaluation/axis-checks/index.js.map +1 -1
  25. package/dist/evaluation/axis-checks/registry.d.ts +2 -2
  26. package/dist/evaluation/axis-checks/registry.js +252 -1
  27. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  28. package/dist/evaluation/index.d.ts +160 -8
  29. package/dist/evaluation/index.js +702 -81
  30. package/dist/evaluation/index.js.map +1 -1
  31. package/dist/fragments/index.js +1 -1
  32. package/dist/fragments/index.js.map +1 -1
  33. package/dist/{generate-8L_bc2iP.d.ts → generate-9IeNT4ZY.d.ts} +1 -1
  34. package/dist/harness/check/runtime-render/index.d.ts +3 -2
  35. package/dist/harness/check/runtime-render/index.js +8 -12
  36. package/dist/harness/check/runtime-render/index.js.map +1 -1
  37. package/dist/harness/index.d.ts +3 -2
  38. package/dist/harness/index.js +385 -254
  39. package/dist/harness/index.js.map +1 -1
  40. package/dist/harness/prompts.d.ts +2 -2
  41. package/dist/harness/prompts.js +3 -3
  42. package/dist/harness/prompts.js.map +1 -1
  43. package/dist/harness/result-types.d.ts +3 -3
  44. package/dist/harness/runtime.d.ts +13 -4
  45. package/dist/harness/runtime.js +106 -19
  46. package/dist/harness/runtime.js.map +1 -1
  47. package/dist/harness/types-public.d.ts +13 -2
  48. package/dist/harness/types-public.js +385 -254
  49. package/dist/harness/types-public.js.map +1 -1
  50. package/dist/harness/validator-trace-sink.d.ts +2 -2
  51. package/dist/index.d.ts +29 -7
  52. package/dist/index.js +4465 -3727
  53. package/dist/index.js.map +1 -1
  54. package/dist/patch.d.ts +3 -2
  55. package/dist/{prompts-k6m6reqe.d.ts → prompts-YU9piOgs.d.ts} +12 -46
  56. package/dist/styling-profile-CxfvV48g.d.ts +35 -0
  57. package/dist/tools/get-primitives-ts.d.ts +1 -1
  58. package/dist/tools/get-primitives-ts.js +1 -1
  59. package/dist/tools/get-primitives-ts.js.map +1 -1
  60. package/dist/{types-public-D24JLJgl.d.ts → types-public-DVSssdc-.d.ts} +39 -3
  61. package/dist/validation/index.d.ts +1 -1
  62. package/dist/validation/index.js +1 -1
  63. package/dist/validation/index.js.map +1 -1
  64. package/dist/workflows.d.ts +3 -2
  65. package/package.json +12 -12
  66. package/src/adapters/generation-dispatch.ts +6 -1
  67. package/src/boilerplate/free-design-prompt.ts +7 -0
  68. package/src/boilerplate/hard-sections.ts +16 -1
  69. package/src/boilerplate/styling-profile.ts +166 -0
  70. package/src/boilerplate/system-prompt.ts +20 -8
  71. package/src/check/run-tier0.ts +2 -0
  72. package/src/contract-context.ts +15 -14
  73. package/src/create-harness.ts +4 -0
  74. package/src/create-ui-generator.ts +41 -2
  75. package/src/design-mode.ts +11 -1
  76. package/src/design-system-docs.ts +7 -4
  77. package/src/effort.ts +48 -0
  78. package/src/evaluation/axis-checks/checks/grid.ts +148 -0
  79. package/src/evaluation/axis-checks/checks/universal.ts +244 -0
  80. package/src/evaluation/axis-checks/dispatch.ts +78 -13
  81. package/src/evaluation/axis-checks/helpers.ts +12 -0
  82. package/src/evaluation/axis-checks/registry.ts +2 -0
  83. package/src/evaluation/design-identity.ts +96 -0
  84. package/src/evaluation/index.ts +17 -0
  85. package/src/evaluation/llm-evaluator.ts +13 -1
  86. package/src/evaluation/theme-css.ts +14 -0
  87. package/src/evaluation/types-public.ts +35 -1
  88. package/src/evaluation/visual-evaluator.ts +478 -45
  89. package/src/fragments/render.ts +1 -1
  90. package/src/harness/check/runtime-render/prepare-mockup.ts +18 -11
  91. package/src/harness/coding/init-session.ts +1 -0
  92. package/src/harness/coding/run-coding-turn.ts +6 -14
  93. package/src/harness/coding/run-eval-round.ts +31 -3
  94. package/src/harness/runtime.ts +10 -0
  95. package/src/harness/types-public.ts +11 -0
  96. package/src/index.ts +2 -0
  97. package/src/pin-fixtures.ts +98 -0
  98. package/src/run-check.ts +10 -10
  99. package/src/tools/get-primitives-ts.ts +1 -1
  100. package/src/tools.ts +2 -1
  101. package/src/validation/primitives.ts +1 -1
@@ -27,7 +27,7 @@ export const renderFragments: Record<string, HarnessFragment> = {
27
27
  value: "grid",
28
28
  cacheTier: "axisDelta",
29
29
  promptText:
30
- "## Render: grid\n2D tile layout. Use <CardGrid> or CSS `display: grid` with `grid-template-columns: repeat(N, 1fr)`. If items carry row/col fields, position each tile at (row, col). Do not scroll horizontally.",
30
+ "## Render: grid\nTwo shapes share this axis — read the contract:\n- A TILE grid (a gallery, a stat grid, items that may carry row/col fields): `<Grid columns={N} gap=\"md\">` (or `<CardGrid>`); if items carry row/col fields, position each tile at (row, col).\n- A BOARD (a columns / lanes / stages array whose items are cards — a kanban): the columns sit SIDE BY SIDE at md and wider — `<Grid columns={{ base: 1, md: columns.length }} gap=\"md\">` wrapping the columns map, each column its own `<Stack>` of `<Card>`s with its header and count; NEVER a `<Stack>` of column sections (that is a list — tier-0 fails it).\nNever hand-roll `display: grid`. Do not scroll horizontally.",
31
31
  },
32
32
  spatial: {
33
33
  axis: "render",
@@ -129,6 +129,16 @@ function synthesizeFromSchema(
129
129
  hint: string,
130
130
  warnings: string[],
131
131
  depth: number,
132
+ /**
133
+ * 1-based position when the value belongs to an array element (inherited
134
+ * by the element's own fields). Every synthesized string inside an element
135
+ * carries it, so two elements never share an id or a visible label — the
136
+ * runtime probe's selection-identity rule ("clicking each fired the action
137
+ * with the same id") is undecidable on identical items and read a correct
138
+ * component as broken (2026-09-10, the 1.6 hello: both quick replies were
139
+ * `Sample Id` / `Sample Label`).
140
+ */
141
+ ordinal?: number,
132
142
  ): SynthOk | SynthFail {
133
143
  if (!schema) return { kind: "fail", reason: "no schema" };
134
144
  if (depth > MAX_DEPTH) return { kind: "fail", reason: "schema too deep" };
@@ -146,10 +156,10 @@ function synthesizeFromSchema(
146
156
 
147
157
  // Union schemas — try first branch only
148
158
  if (schema.oneOf && schema.oneOf.length > 0) {
149
- return synthesizeFromSchema(schema.oneOf[0], hint, warnings, depth + 1);
159
+ return synthesizeFromSchema(schema.oneOf[0], hint, warnings, depth + 1, ordinal);
150
160
  }
151
161
  if (schema.anyOf && schema.anyOf.length > 0) {
152
- return synthesizeFromSchema(schema.anyOf[0], hint, warnings, depth + 1);
162
+ return synthesizeFromSchema(schema.anyOf[0], hint, warnings, depth + 1, ordinal);
153
163
  }
154
164
 
155
165
  // Draft-07 type array (`['string','null']` — the canonical nullable
@@ -158,12 +168,7 @@ function synthesizeFromSchema(
158
168
  // above bounds the recursion.
159
169
  if (Array.isArray(schema.type)) {
160
170
  const primary = schema.type.find((t) => t !== "null") ?? "null";
161
- return synthesizeFromSchema(
162
- { ...schema, type: primary },
163
- hint,
164
- warnings,
165
- depth + 1,
166
- );
171
+ return synthesizeFromSchema({ ...schema, type: primary }, hint, warnings, depth + 1, ordinal);
167
172
  }
168
173
 
169
174
  switch (schema.type) {
@@ -184,7 +189,9 @@ function synthesizeFromSchema(
184
189
  // Use field name as a hint so it appears in DOM and prop-coverage check sees it.
185
190
  // Capitalize first letter for natural-looking placeholder.
186
191
  const cap = hint.charAt(0).toUpperCase() + hint.slice(1);
187
- return { kind: "ok", value: `Sample ${cap}`, source: "schema-synth" };
192
+ // Inside an array element every string is distinct per element.
193
+ const value = ordinal === undefined ? `Sample ${cap}` : `Sample ${cap} ${ordinal}`;
194
+ return { kind: "ok", value, source: "schema-synth" };
188
195
  }
189
196
  }
190
197
  }
@@ -211,7 +218,7 @@ function synthesizeFromSchema(
211
218
  const items: JsonValue[] = [];
212
219
  for (let i = 0; i < 2; i++) {
213
220
  const itemHint = `${hint}Item${i + 1}`;
214
- const itemSynth = synthesizeFromSchema(schema.items, itemHint, warnings, depth + 1);
221
+ const itemSynth = synthesizeFromSchema(schema.items, itemHint, warnings, depth + 1, i + 1);
215
222
  if (itemSynth.kind === "ok") {
216
223
  // Inject an `id` for list-key purposes if the item is an object lacking one.
217
224
  if (
@@ -233,7 +240,7 @@ function synthesizeFromSchema(
233
240
  const propsMap = schema.properties ?? {};
234
241
  const required = new Set(schema.required ?? []);
235
242
  for (const [k, sub] of Object.entries(propsMap)) {
236
- const subSynth = synthesizeFromSchema(sub, k, warnings, depth + 1);
243
+ const subSynth = synthesizeFromSchema(sub, k, warnings, depth + 1, ordinal);
237
244
  if (subSynth.kind === "ok") {
238
245
  obj[k] = subSynth.value;
239
246
  } else if (required.has(k)) {
@@ -169,6 +169,7 @@ export async function initSession(input: {
169
169
  contract: params.contract,
170
170
  designContext: undefined,
171
171
  designMode: harness.designMode,
172
+ profile: harness.profile,
172
173
  },
173
174
  {
174
175
  provider: mapProviderForEvaluator(evalSpec.provider),
@@ -33,6 +33,7 @@ import {
33
33
  WRITE_PLAN_TOOL,
34
34
  type LLMToolDef,
35
35
  } from "../../tools.js";
36
+ import { LUCIDE_ICON_NAMES } from "@ggui-ai/design";
36
37
  import { formatWithHashlines } from "../hashline.js";
37
38
  import {
38
39
  recordToolExchange,
@@ -592,20 +593,11 @@ ${closingInstruction}`;
592
593
  // Handle get_available_icons — return icon list without consuming a turn
593
594
  if (call.name === "get_available_icons") {
594
595
  if (!iconNamesCache) {
595
- try {
596
- // Dynamic import — resolve from design package dist
597
- const iconDataPath = new URL(
598
- "../../../../packages/design/dist/primitives/icon-data.js",
599
- import.meta.url,
600
- ).pathname;
601
- const { LUCIDE_ICONS } = await import(iconDataPath);
602
- iconNamesCache = Object.keys(LUCIDE_ICONS)
603
- .map((n) => n.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase())
604
- .join(", ");
605
- } catch {
606
- iconNamesCache =
607
- "sun, moon, cloud, cloud-rain, heart, star, search, home, plus, minus, check, x, arrow-left, arrow-right, chevron-down, chevron-up, menu, user, settings, bell, mail, phone, shopping-cart, download, upload, edit, trash-2, save, eye, lock, globe, map-pin, calendar, clock, zap, alert-circle, info, help-circle";
608
- }
596
+ // The one list the Icon primitive renders (ggui#1015) — the same
597
+ // export the check leg reads, so the tool never advertises a name
598
+ // the primitive does not know (the former dist-relative dynamic
599
+ // import fell back to a 40-name string whenever the path missed).
600
+ iconNamesCache = LUCIDE_ICON_NAMES.join(", ");
609
601
  }
610
602
  console.log(`[simple] turn ${turnsUsed}: get_available_icons`);
611
603
  let iconResult = `Available Lucide icon names (use with <Icon name="...">):\n${iconNamesCache}`;
@@ -128,6 +128,9 @@ const MAX_FEEDBACK_ISSUES = 3;
128
128
  * criterion is `not-applicable` (excluded from coverage denominators),
129
129
  * not `skipped` (which would count as silent absence).
130
130
  */
131
+ /** Why an eval result carries no criteria: the round itself threw (ggui#1110). */
132
+ const EVAL_ROUND_THREW_REASON = "eval round threw";
133
+
131
134
  const LOW_RISK_BYPASS_REASON =
132
135
  "same-image low-risk bypass: axis checks clean, tier-1/2 evaluation not invoked";
133
136
 
@@ -457,6 +460,7 @@ export async function runEvalRound(
457
460
  // the source (free mode: color-only tokens, arm-neutral
458
461
  // visual judging).
459
462
  designMode: harness.designMode,
463
+ profile: harness.profile,
460
464
  },
461
465
  {
462
466
  provider: mapProviderForEvaluator(evaluationAgent.provider),
@@ -480,12 +484,20 @@ export async function runEvalRound(
480
484
  : null,
481
485
  visualMod
482
486
  ? visualMod.runVisualEval(
483
- { compiledCode, originalPrompt: userPrompt },
487
+ {
488
+ compiledCode,
489
+ originalPrompt: userPrompt,
490
+ profile: harness.profile,
491
+ // The caller's theme, when named — the round paints what the app paints.
492
+ ...(visualEvaluation?.cssTokens !== undefined ? { cssTokens: visualEvaluation.cssTokens } : {}),
493
+ },
484
494
  {
485
495
  provider: mapProviderForEvaluator(visualEvalAgent.provider) as "claude" | "google",
486
496
  model: visualEvalAgent.model,
487
497
  passThreshold: visualThreshold,
488
- sampleProps: visualEvaluation?.sampleProps,
498
+ // The in-loop visual round renders the same sample the runtime probe
499
+ // does (the caller's fixture) unless the config names its own.
500
+ sampleProps: visualEvaluation?.sampleProps ?? fixtureProps,
489
501
  viewport: visualEvaluation?.viewport,
490
502
  // Per-canvas judging (arm-neutral) — absent = single shot.
491
503
  ...(visualEvaluation?.canvases !== undefined
@@ -881,7 +893,23 @@ export async function runEvalRound(
881
893
  lastDiffFailed: false,
882
894
  };
883
895
  } catch (e) {
884
- console.error("[simple] eval failed:", e instanceof Error ? e.message : e);
896
+ // The round threw. Until ggui#1110 this returned `evalResult` UNDEFINED and
897
+ // said so only in a console line: downstream the generation carried no
898
+ // evaluation at all, an offline mint exported no `eval.json`, and the bar
899
+ // read the missing file as "probe skipped" — a thrown evaluation and an
900
+ // evaluation that never ran became the same fact, with the reason lost.
901
+ // Now the failure IS the result: the round reports what threw, on the
902
+ // channel a judge reads, and can never be mistaken for a pass (`pass` is
903
+ // empty, the probe status is `infra-skipped`, and a bar that requires the
904
+ // probe still refuses — with a cause instead of a silence).
905
+ const message = e instanceof Error ? e.message : String(e);
906
+ console.error("[simple] eval failed:", message);
907
+ evalResult = evalResult ?? {
908
+ issues: [],
909
+ pass: [],
910
+ criteriaCoverage: notApplicableCoverage(`${EVAL_ROUND_THREW_REASON}: ${message}`),
911
+ runtimeProbe: { status: "infra-skipped", reason: `${EVAL_ROUND_THREW_REASON}: ${message}` },
912
+ };
885
913
  return {
886
914
  control: "break",
887
915
  evalDone: false,
@@ -24,6 +24,7 @@
24
24
  // directly to the coding agent in the system prompt — there is no
25
25
  // separate planner stage.
26
26
 
27
+ import type { GenerationProfileInput } from "../boilerplate/styling-profile.js";
27
28
  import type { QualityConfig } from "../evaluation/types-public.js";
28
29
  import { DEFAULT_DESIGN_SYSTEM_DOCS } from "../design-system-docs.js";
29
30
  import { PRIMITIVES_DOCUMENTATION } from "../validation/index.js";
@@ -90,6 +91,12 @@ export interface SingleComponentParams {
90
91
  * today's single screenshot at `viewport`.
91
92
  */
92
93
  canvases?: readonly CanvasClass[];
94
+ /**
95
+ * The stylesheet the in-loop visual round renders under (a caller's
96
+ * theme composed by `cssTokensForAppTheme`); absent = the design's
97
+ * default tokens. Never part of the prompt or the identity.
98
+ */
99
+ cssTokens?: string;
93
100
  };
94
101
  onProgress?: (event: unknown) => void;
95
102
  onInitialResult?: (result: {
@@ -149,6 +156,8 @@ export function buildSystemPrompt(
149
156
  designMode?: DesignMode,
150
157
  /** Rendering canvas class (`free` mode) — see `SystemPromptInputs.canvas`. */
151
158
  canvas?: CanvasClass,
159
+ /** The app's generation profile (#991). */
160
+ profile?: GenerationProfileInput,
152
161
  ): string {
153
162
  return buildSystemPromptSkeleton({
154
163
  userRequest,
@@ -163,6 +172,7 @@ export function buildSystemPrompt(
163
172
  gadgetTypes,
164
173
  designMode,
165
174
  canvas,
175
+ profile,
166
176
  // criteriaBlock left undefined — ui-gen fills default from open CRITERIA.
167
177
  });
168
178
  }
@@ -18,6 +18,7 @@
18
18
  // types hub for the harness module. `AxisCheck` + `AxisCheckInput` live
19
19
  // in `@ggui-ai/ui-gen/evaluation`.
20
20
 
21
+ import type { GenerationProfileInput } from "../boilerplate/styling-profile.js";
21
22
  import type { GadgetDescriptor, DataContract, JsonValue } from "@ggui-ai/protocol";
22
23
  import type { Classification } from "../classifier/axes.js";
23
24
  import type { EvalIssue, EvalTier, AxisCheck, RuntimeProbeStatus } from "../evaluation/types-public.js";
@@ -302,6 +303,8 @@ export interface Harness {
302
303
  * into `free`.
303
304
  */
304
305
  readonly designMode: DesignMode;
306
+ /** The app's generation profile in force for this harness (#991), if any. */
307
+ readonly profile?: GenerationProfileInput;
305
308
 
306
309
  readonly how: HowLeg;
307
310
  readonly what: WhatLeg;
@@ -396,6 +399,8 @@ export type SystemPromptBuilder = (input: {
396
399
  readonly designMode?: DesignMode;
397
400
  /** Rendering canvas class (`free` mode) — see `CreateHarnessInput.canvas`. */
398
401
  readonly canvas?: CanvasClass;
402
+ /** The app's generation profile (#991) — see `CreateHarnessInput.profile`. */
403
+ readonly profile?: GenerationProfileInput;
399
404
  }) => string;
400
405
 
401
406
  export interface CreateHarnessInput {
@@ -419,6 +424,12 @@ export interface CreateHarnessInput {
419
424
  * by the `constrained` prompt.
420
425
  */
421
426
  readonly canvas?: CanvasClass;
427
+ /**
428
+ * The app's generation profile (#991): operator-declared free text the
429
+ * HOW leg renders as one bounded section and every judge reads under its
430
+ * own frame. Absent = byte-identical prompts.
431
+ */
432
+ readonly profile?: GenerationProfileInput;
422
433
  /**
423
434
  * Pre-resolved static harness policy. When omitted, `createHarness`
424
435
  * falls back to `DEFAULT_HARNESS_POLICY` from `@ggui-ai/ui-gen/policy`.
package/src/index.ts CHANGED
@@ -82,3 +82,5 @@ export type {
82
82
  // go through this helper instead of `new Anthropic(...)` so future
83
83
  // header / baseURL tweaks land in one place.
84
84
  export { createAnthropicClient } from './adapters/claude/client.js';
85
+ export { AESTHETIC_BRIEF_MAX_CHARS, type GenerationProfileInput } from './boilerplate/styling-profile.js';
86
+ export { EFFORT_DIALS, EFFORT_TABLE_VERSION, effortDials, type EffortDials } from './effort.js';
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The two fixed fixtures every prompt pin renders — one action-bearing
3
+ * `chat`×`mobile` shell, one `fullscreen`×`desktop` board with a
4
+ * `contextSpec` — shared by `design-mode.pin.test.ts` (the stable prefix)
5
+ * and `shape-guidance.pin.test.ts` (the axis-conditioned Shape Guidance).
6
+ */
7
+ import type { DataContract } from '@ggui-ai/protocol';
8
+
9
+ export interface PinFixture {
10
+ readonly userRequest: string;
11
+ readonly shellType: 'chat' | 'fullscreen' | 'spatial';
12
+ readonly screen: 'mobile' | 'tablet' | 'desktop' | 'universal';
13
+ readonly contract: DataContract;
14
+ }
15
+
16
+ export const PIN_FIXTURES: readonly PinFixture[] = [
17
+ {
18
+ userRequest: 'A todo list where each item can be toggled done',
19
+ shellType: 'chat',
20
+ screen: 'mobile',
21
+ contract: {
22
+ propsSpec: {
23
+ properties: {
24
+ todos: {
25
+ required: true,
26
+ description: 'The todos to show',
27
+ schema: {
28
+ type: 'array',
29
+ items: {
30
+ type: 'object',
31
+ properties: {
32
+ id: { type: 'string' },
33
+ title: { type: 'string' },
34
+ done: { type: 'boolean' },
35
+ },
36
+ },
37
+ },
38
+ },
39
+ },
40
+ },
41
+ actionSpec: {
42
+ toggleTodo: {
43
+ label: 'Toggle',
44
+ description: 'Flip the done state of one todo',
45
+ schema: {
46
+ type: 'object',
47
+ properties: { id: { type: 'string' } },
48
+ required: ['id'],
49
+ },
50
+ },
51
+ },
52
+ },
53
+ },
54
+ {
55
+ userRequest: 'A kanban board with live task updates and a selected card',
56
+ shellType: 'fullscreen',
57
+ screen: 'desktop',
58
+ contract: {
59
+ propsSpec: {
60
+ properties: {
61
+ columns: {
62
+ required: true,
63
+ schema: { type: 'array', items: { type: 'string' } },
64
+ },
65
+ tasks: {
66
+ required: false,
67
+ schema: {
68
+ type: 'array',
69
+ items: {
70
+ type: 'object',
71
+ properties: {
72
+ id: { type: 'string' },
73
+ column: { type: 'string' },
74
+ title: { type: 'string' },
75
+ },
76
+ },
77
+ },
78
+ },
79
+ },
80
+ },
81
+ streamSpec: {
82
+ taskUpdates: {
83
+ description: 'Task CRUD feed',
84
+ schema: {
85
+ type: 'object',
86
+ properties: {
87
+ action: { type: 'string', enum: ['create', 'move', 'edit', 'delete'] },
88
+ id: { type: 'string' },
89
+ },
90
+ },
91
+ },
92
+ },
93
+ contextSpec: {
94
+ selectedId: { schema: { type: 'string', nullable: true } },
95
+ },
96
+ },
97
+ },
98
+ ];
package/src/run-check.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  newValidatorTraceId,
23
23
  truncateSourceForTrace,
24
24
  } from "./harness/validator-trace-sink.js";
25
+ import { runGatedAxisChecks, traceAxisChecksSkipped } from "./evaluation/axis-checks/dispatch.js";
25
26
 
26
27
  export interface RunCheckInput {
27
28
  readonly sourceCode: string;
@@ -66,6 +67,10 @@ export async function runCheck(input: RunCheckInput): Promise<CheckResult> {
66
67
  const startedAt = Date.now();
67
68
 
68
69
  if (compiledCode === null) {
70
+ traceAxisChecksSkipped(
71
+ { sourceCode, contract, originalPrompt: prompt, classification: harness.classification, designMode: harness.designMode },
72
+ "compiledCode null — no check ran",
73
+ );
69
74
  const result: CheckResult = {
70
75
  issues: [],
71
76
  axisIssueCount: 0,
@@ -115,16 +120,11 @@ export async function runCheck(input: RunCheckInput): Promise<CheckResult> {
115
120
  classification: harness.classification,
116
121
  designMode: harness.designMode,
117
122
  };
118
- let axisIssueCount = 0;
119
- const seenAxisIds = new Set<string>();
120
- for (const axisCheck of check.axisChecks) {
121
- if (seenAxisIds.has(axisCheck.id)) continue;
122
- seenAxisIds.add(axisCheck.id);
123
- firedIds.push(axisCheck.id);
124
- const axisIssues = axisCheck.run(axisInput);
125
- issues.push(...axisIssues);
126
- axisIssueCount += axisIssues.length;
127
- }
123
+ // The one runner `runAxisChecks` shares (ggui#1046 trace prints here, on the served path).
124
+ const axisRun = runGatedAxisChecks(check.axisChecks, axisInput);
125
+ issues.push(...axisRun.issues);
126
+ firedIds.push(...axisRun.firedIds);
127
+ const axisIssueCount = axisRun.issues.length;
128
128
 
129
129
  // ── Tier checks (deterministic, harness-owned) ────────────────────────
130
130
  let tierIssueCount = 0;