@ggui-ai/ui-gen 0.23.0 → 0.24.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-Du7P_5Fx.d.ts +201 -0
- package/dist/adapters/generation-dispatch.d.ts +33 -12
- package/dist/adapters/generation-dispatch.js +510 -128
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +5 -3
- package/dist/advanced/index.js +551 -142
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +153 -59
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +2 -1
- package/dist/check/index.js +47 -1
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-D0kbWwln.d.ts → create-ui-generator-GIhzx08x.d.ts} +34 -10
- package/dist/{dispatch-wxaIMwZt.d.ts → dispatch-BnAH56p4.d.ts} +1 -1
- package/dist/evaluation/axis-checks/index.d.ts +4 -3
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +2 -1
- package/dist/evaluation/index.d.ts +4 -3
- package/dist/evaluation/index.js.map +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +29 -10
- 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 +2 -1
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +2 -1
- package/dist/harness/runtime.d.ts +2 -1
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +11 -2
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +2 -1
- package/dist/index.d.ts +38 -3
- package/dist/index.js +553 -143
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +2 -1
- package/dist/policy.d.ts +7 -4
- package/dist/policy.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-public-Ow-tNtFE.d.ts → types-public-Bk6d1WLN.d.ts} +77 -11
- package/dist/workflows.d.ts +2 -1
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +137 -96
- package/src/check/run-tier0.ts +80 -1
- package/src/create-ui-generator.ts +185 -95
- package/src/evaluation/types-public.ts +73 -11
- 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 +57 -4
- package/src/harness/coding/init-session.ts +13 -0
- package/src/harness/coding/run-eval-round.ts +238 -15
- package/src/harness/policy.ts +11 -0
- package/src/harness/primitive-slice.ts +104 -0
- package/src/harness/types-public.ts +9 -1
- package/src/index.ts +4 -0
- package/src/policy.ts +7 -4
- package/src/tools/render-check-worker.ts +74 -23
- package/dist/render-check-Blwj8gB6.d.ts +0 -105
|
@@ -33,11 +33,15 @@ import { buildSystemPrompt as buildSystemPromptWithFunnel } from "../harness/run
|
|
|
33
33
|
// predicate against the full registry and passes the filtered result.
|
|
34
34
|
import { REGISTRY as AXIS_CHECK_REGISTRY } from "../evaluation/axis-checks/registry.js";
|
|
35
35
|
import { matches as axisCheckMatches } from "../evaluation/types-public.js";
|
|
36
|
-
// Runtime-render
|
|
37
|
-
// optional runtimeRender; this callsite
|
|
38
|
-
// `
|
|
39
|
-
// backed contract-wiring verification
|
|
36
|
+
// Runtime-render check. The `createHarness` constructor accepts an
|
|
37
|
+
// optional runtimeRender; this callsite wires the caller's instance
|
|
38
|
+
// (`params.runtimeRender`) or, absent one, `DEFAULT_RUNTIME_RENDER_CHECK`,
|
|
39
|
+
// so production keeps the happy-dom-backed contract-wiring verification
|
|
40
|
+
// step. The dispatch never BUILDS a check: the instance carries the
|
|
41
|
+
// concurrency cap, so its lifetime must be the caller's (one per
|
|
42
|
+
// generator), never one per generation.
|
|
40
43
|
import { DEFAULT_RUNTIME_RENDER_CHECK } from "../harness/check/runtime-render/adapter.js";
|
|
44
|
+
import type { RuntimeRenderCheck } from "../harness/types-public.js";
|
|
41
45
|
import { warmupRuntimeRenderProbe } from "../harness/check/runtime-render/index.js";
|
|
42
46
|
import { assembleGenerationResult } from "../harness/coding/assemble-result.js";
|
|
43
47
|
import {
|
|
@@ -54,10 +58,7 @@ import type {
|
|
|
54
58
|
import type { ProviderName, ToolDefinition } from "./types.js";
|
|
55
59
|
import type { QualityConfig } from "../evaluation/types-public.js";
|
|
56
60
|
import type { AgentConfig } from "../harness/llm-router.js";
|
|
57
|
-
import {
|
|
58
|
-
type GadgetDescriptor,
|
|
59
|
-
type JsonObject,
|
|
60
|
-
} from "@ggui-ai/protocol";
|
|
61
|
+
import { type GadgetDescriptor, type JsonObject } from "@ggui-ai/protocol";
|
|
61
62
|
import { canvasForRendering, type CanvasClass, type DesignMode } from "../design-mode.js";
|
|
62
63
|
import type { GenerationProfileInput } from "../boilerplate/styling-profile.js";
|
|
63
64
|
|
|
@@ -118,20 +119,38 @@ export interface GenerationDispatchParams {
|
|
|
118
119
|
*/
|
|
119
120
|
fixtureProps?: JsonObject;
|
|
120
121
|
/**
|
|
121
|
-
* Whether to wire `
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
122
|
+
* Whether to wire the runtime-render check (`runtimeRender` when given,
|
|
123
|
+
* else `DEFAULT_RUNTIME_RENDER_CHECK`) into the harness's check leg.
|
|
124
|
+
* When `true` (default), the runtime-render probe runs ONCE
|
|
125
|
+
* at the exit decision, after the coding turns, in an isolated
|
|
126
|
+
* subprocess — never on the per-turn hot path. Without an `evaluation`
|
|
127
|
+
* config that round is observe + one repair turn (ggui#1380): a
|
|
128
|
+
* recoverable render crash buys exactly one repair turn and one
|
|
129
|
+
* re-probe, then the generation serves what it has; a probe that timed
|
|
130
|
+
* out, could not run, or had nothing to probe never blocks and never
|
|
131
|
+
* repairs. With an `evaluation` config the probe is the evaluation
|
|
132
|
+
* round's exit gate. Its status and timing land on
|
|
133
|
+
* `evalResult.runtimeProbe` (and on the generation's metadata through
|
|
134
|
+
* `createUiGenerator`).
|
|
125
135
|
*
|
|
126
|
-
* When `false`, the harness omits runtime-render
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* Bench callers can run the probe externally as a final post-gen
|
|
130
|
-
* check to get pass/fail visibility without slowing the loop.
|
|
136
|
+
* When `false`, the harness omits runtime-render entirely: no probe
|
|
137
|
+
* round runs, and a caller can run the probe externally as a final
|
|
138
|
+
* post-generation check instead.
|
|
131
139
|
*
|
|
132
140
|
* Default: `true`.
|
|
133
141
|
*/
|
|
134
142
|
enableRuntimeRender?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* The runtime-render check to wire (ggui#1380) — built by the CALLER with
|
|
145
|
+
* `createRuntimeRenderCheck(config)` and kept for the caller's lifetime,
|
|
146
|
+
* because the check instance owns the probe's concurrency cap: one
|
|
147
|
+
* instance per generator bounds the workers a serving deployment has
|
|
148
|
+
* live at once; one per dispatch would give every generation its own
|
|
149
|
+
* free slots and bound nothing. Absent ⇒ `DEFAULT_RUNTIME_RENDER_CHECK`
|
|
150
|
+
* (the evaluation lane's bounds, no concurrency cap). Only read when
|
|
151
|
+
* `enableRuntimeRender` is on.
|
|
152
|
+
*/
|
|
153
|
+
runtimeRender?: RuntimeRenderCheck;
|
|
135
154
|
/**
|
|
136
155
|
* Operator-registered gadget catalog forwarded to the
|
|
137
156
|
* code-gen system prompt's `clientCapabilities — registered
|
|
@@ -200,7 +219,9 @@ export interface GenerationDispatchParams {
|
|
|
200
219
|
* Map ProviderName ('claude' | 'openai' | 'google') to AgentConfig provider ('anthropic' | 'openai' | 'google').
|
|
201
220
|
* The LLM router uses 'anthropic' while the rest of the codebase uses 'claude'.
|
|
202
221
|
*/
|
|
203
|
-
function mapProviderForLLMRouter(
|
|
222
|
+
function mapProviderForLLMRouter(
|
|
223
|
+
provider: ProviderName
|
|
224
|
+
): "anthropic" | "openai" | "google" | "openrouter" {
|
|
204
225
|
return provider === "claude" ? "anthropic" : provider;
|
|
205
226
|
}
|
|
206
227
|
|
|
@@ -209,7 +230,7 @@ function mapProviderForLLMRouter(provider: ProviderName): "anthropic" | "openai"
|
|
|
209
230
|
* provider-agnostic.
|
|
210
231
|
*/
|
|
211
232
|
export async function dispatchGeneration(
|
|
212
|
-
params: GenerationDispatchParams
|
|
233
|
+
params: GenerationDispatchParams
|
|
213
234
|
): Promise<GenerationResult> {
|
|
214
235
|
// NOTE: userPrompt arrives already enriched with rendering context + contract
|
|
215
236
|
// (including examples) from the caller (runner.ts / generator.ts).
|
|
@@ -294,17 +315,13 @@ export async function dispatchGeneration(
|
|
|
294
315
|
// from '<package>'` lines for thin contract refs. The closure
|
|
295
316
|
// capture below (systemPromptBuilder) wires the SAME catalog
|
|
296
317
|
// into the HOW leg.
|
|
297
|
-
...(params.appGadgets !== undefined
|
|
298
|
-
? { appGadgets: params.appGadgets }
|
|
299
|
-
: {}),
|
|
318
|
+
...(params.appGadgets !== undefined ? { appGadgets: params.appGadgets } : {}),
|
|
300
319
|
// Forward the third-party wrapper `.d.ts` map so
|
|
301
320
|
// `WhatLeg.gadgetTypes` reaches the coding-agent's typecheck
|
|
302
321
|
// overlay (autoCommit → runTier0Checks → typecheck). The
|
|
303
322
|
// systemPromptBuilder closure below also captures `gadgetTypes`
|
|
304
323
|
// from `params` directly for the prompt `Type:` lines.
|
|
305
|
-
...(params.gadgetTypes !== undefined
|
|
306
|
-
? { gadgetTypes: params.gadgetTypes }
|
|
307
|
-
: {}),
|
|
324
|
+
...(params.gadgetTypes !== undefined ? { gadgetTypes: params.gadgetTypes } : {}),
|
|
308
325
|
// Design mode + canvas — the harness carries them to every leg.
|
|
309
326
|
...(params.designMode !== undefined ? { designMode: params.designMode } : {}),
|
|
310
327
|
// ggui#1117 — the canvas is ALWAYS carried: a stated one, else the class
|
|
@@ -334,7 +351,15 @@ export async function dispatchGeneration(
|
|
|
334
351
|
// prompt's gadget catalog renders a `Type:` line per third-party
|
|
335
352
|
// gadget (the LLM sees a wrapper's real call shape it otherwise
|
|
336
353
|
// can't know).
|
|
337
|
-
systemPromptBuilder: ({
|
|
354
|
+
systemPromptBuilder: ({
|
|
355
|
+
userRequest,
|
|
356
|
+
shellType,
|
|
357
|
+
screen,
|
|
358
|
+
axisDelta,
|
|
359
|
+
designMode,
|
|
360
|
+
canvas,
|
|
361
|
+
profile,
|
|
362
|
+
}) =>
|
|
338
363
|
buildSystemPromptWithFunnel(
|
|
339
364
|
userRequest,
|
|
340
365
|
shellType,
|
|
@@ -344,29 +369,30 @@ export async function dispatchGeneration(
|
|
|
344
369
|
params.gadgetTypes,
|
|
345
370
|
designMode,
|
|
346
371
|
canvas,
|
|
347
|
-
profile
|
|
372
|
+
profile
|
|
348
373
|
),
|
|
349
374
|
// Pre-filtered axis-check registry. The `matches()` predicate
|
|
350
375
|
// selects the checks relevant to this generation's axis vector.
|
|
351
376
|
axisChecks: AXIS_CHECK_REGISTRY.filter((check) =>
|
|
352
|
-
axisCheckMatches(classification.vector, check)
|
|
377
|
+
axisCheckMatches(classification.vector, check)
|
|
353
378
|
),
|
|
354
|
-
// The
|
|
355
|
-
// design) stay on the dispatch side of
|
|
356
|
-
// createHarness defaults this to undefined.
|
|
379
|
+
// The runtime-render check, built under the caller's probe config.
|
|
380
|
+
// Heavy deps (happy-dom + wire + design) stay on the dispatch side of
|
|
381
|
+
// the boundary; the skeleton createHarness defaults this to undefined.
|
|
357
382
|
//
|
|
358
383
|
// Gated behind `enableRuntimeRender` (default true) so
|
|
359
384
|
// benchmark/dev callers can opt out of in-loop probe execution and
|
|
360
385
|
// run the probe externally as a final post-generation check.
|
|
361
|
-
runtimeRender:
|
|
362
|
-
|
|
363
|
-
|
|
386
|
+
runtimeRender:
|
|
387
|
+
(params.enableRuntimeRender ?? true)
|
|
388
|
+
? (params.runtimeRender ?? DEFAULT_RUNTIME_RENDER_CHECK)
|
|
389
|
+
: undefined,
|
|
364
390
|
});
|
|
365
391
|
console.log(
|
|
366
|
-
`[harness] id=${harness.id} name=${harness.name} workflow=${harness.process.workflow.name} overrides=${harness.meta.overrides.join(",") || "none"}
|
|
392
|
+
`[harness] id=${harness.id} name=${harness.name} workflow=${harness.process.workflow.name} overrides=${harness.meta.overrides.join(",") || "none"}`
|
|
367
393
|
);
|
|
368
394
|
console.log(
|
|
369
|
-
`[simple] axis-vector: ${JSON.stringify(classification.vector)} risk=${classification.riskTier}
|
|
395
|
+
`[simple] axis-vector: ${JSON.stringify(classification.vector)} risk=${classification.riskTier}`
|
|
370
396
|
);
|
|
371
397
|
|
|
372
398
|
// ── Guard: staged workflow is not wired on the dispatch path. ──
|
|
@@ -392,7 +418,7 @@ export async function dispatchGeneration(
|
|
|
392
418
|
`This is a misconfiguration (probably pickProcessMode returning WORKFLOWS.staged). ` +
|
|
393
419
|
`For staged-like behaviour, set process.mode="staged" + workflow.name="single_pass" instead — the A1 ` +
|
|
394
420
|
`scaffold→fill phase machine lives inside the generate runner and fires without the workflow topology. ` +
|
|
395
|
-
`See ralph #3 report (2026-04-14) for the full rationale
|
|
421
|
+
`See ralph #3 report (2026-04-14) for the full rationale.`
|
|
396
422
|
);
|
|
397
423
|
}
|
|
398
424
|
|
|
@@ -409,29 +435,58 @@ export async function dispatchGeneration(
|
|
|
409
435
|
// resolveRunPolicyForProfile falls through to the identity
|
|
410
436
|
// resolveRunPolicy — byte-identical to the default behavior.
|
|
411
437
|
const { resolveRunPolicyForProfile } = await import("../harness/policy.js");
|
|
412
|
-
const resolvedPolicy = resolveRunPolicyForProfile(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
);
|
|
438
|
+
const resolvedPolicy = resolveRunPolicyForProfile(process.env.GGUI_POLICY_PROFILE, harness, {
|
|
439
|
+
provider: routerProvider,
|
|
440
|
+
modelId: resolvedModel,
|
|
441
|
+
});
|
|
417
442
|
|
|
418
|
-
// ──
|
|
419
|
-
//
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
//
|
|
443
|
+
// ── The primitives reference the first-turn prompt carries ──
|
|
444
|
+
// The system prompt embeds PRIMITIVES_DOCUMENTATION (markdown) verbatim.
|
|
445
|
+
// Three policy knobs decide what is SENT in its place, composed in this
|
|
446
|
+
// order (ggui#1324, speed/002 — the slice applies to the format in force;
|
|
447
|
+
// slicing the markdown before the TS swap sent a sliced markdown doc about
|
|
448
|
+
// the size of the full TS doc, which speed/001 measured as a trim):
|
|
449
|
+
// 1. primitiveDocFormat: "markdown" (the embedded doc) or "ts" (its
|
|
450
|
+
// TypeScript-interface form, ~55% smaller, same information);
|
|
451
|
+
// 2. primitiveDocSlice: "full", or an axis-keyed allowlist from the
|
|
452
|
+
// classification ("axis-keyed", or "axis-keyed+contract" which adds the
|
|
453
|
+
// inputs the contract implies) applied to THAT format's doc;
|
|
454
|
+
// 3. primitiveIndex: a name-only index that replaces whatever is left
|
|
455
|
+
// (tool-driven docs; wins when on).
|
|
456
|
+
// Every cell logs `[simple] primitive-doc-bytes … mode=…` (the slice) and
|
|
457
|
+
// `[simple] system-prompt-bytes … doc-format=… doc-bytes=…` (what is sent),
|
|
458
|
+
// so a prompt-size claim is auditable from the log, not inferred.
|
|
426
459
|
let systemPromptOverride: string | undefined;
|
|
427
460
|
{
|
|
428
461
|
const { PRIMITIVES_DOCUMENTATION } = await import("../validation/index.js");
|
|
429
462
|
const fullBytes = Buffer.byteLength(PRIMITIVES_DOCUMENTATION, "utf8");
|
|
463
|
+
const docFormat = resolvedPolicy.context.primitiveDocFormat ?? "markdown";
|
|
464
|
+
const indexMode = resolvedPolicy.context.primitiveIndex ?? "off";
|
|
430
465
|
const mode = resolvedPolicy.context.primitiveDocSlice ?? "full";
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
466
|
+
|
|
467
|
+
let doc = PRIMITIVES_DOCUMENTATION;
|
|
468
|
+
if (docFormat === "ts" && indexMode === "off") {
|
|
469
|
+
const { PRIMITIVES_DOCUMENTATION_TS } = await import("../tools/get-primitives-ts.js");
|
|
470
|
+
doc = PRIMITIVES_DOCUMENTATION_TS;
|
|
471
|
+
const tsBytes = Buffer.byteLength(doc, "utf8");
|
|
472
|
+
console.log(
|
|
473
|
+
`[simple] primitive-doc-ts-bytes=${tsBytes} baseline=${fullBytes} drop=${((1 - tsBytes / fullBytes) * 100).toFixed(1)}%`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
const formatBytes = Buffer.byteLength(doc, "utf8");
|
|
477
|
+
|
|
478
|
+
if (mode === "axis-keyed" || mode === "axis-keyed+contract") {
|
|
479
|
+
const {
|
|
480
|
+
computePrimitiveAllowlist,
|
|
481
|
+
computeContractPrimitives,
|
|
482
|
+
slicePrimitiveDocumentation,
|
|
483
|
+
slicePrimitiveDocumentationTs,
|
|
484
|
+
} = await import("../harness/primitive-slice.js");
|
|
434
485
|
let allowlist = computePrimitiveAllowlist(classification);
|
|
486
|
+
if (mode === "axis-keyed+contract") {
|
|
487
|
+
const fromContract = computeContractPrimitives(effectiveContract);
|
|
488
|
+
allowlist = [...new Set([...allowlist, ...fromContract])].sort();
|
|
489
|
+
}
|
|
435
490
|
// Apply policy-driven excludes — some policy profiles drop
|
|
436
491
|
// near-synonym layout primitives (e.g. Row/Box/Spacer).
|
|
437
492
|
const excludes = resolvedPolicy.context.primitiveDocExcludes ?? [];
|
|
@@ -439,61 +494,41 @@ export async function dispatchGeneration(
|
|
|
439
494
|
const excludeSet = new Set(excludes);
|
|
440
495
|
allowlist = allowlist.filter((p) => !excludeSet.has(p));
|
|
441
496
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const slicedBytes = Buffer.byteLength(
|
|
497
|
+
doc =
|
|
498
|
+
docFormat === "ts" && indexMode === "off"
|
|
499
|
+
? slicePrimitiveDocumentationTs(doc, allowlist)
|
|
500
|
+
: slicePrimitiveDocumentation(doc, allowlist);
|
|
501
|
+
const slicedBytes = Buffer.byteLength(doc, "utf8");
|
|
447
502
|
const excludeTag = excludes.length ? ` excludes=${excludes.join(",")}` : "";
|
|
448
503
|
console.log(
|
|
449
|
-
`[simple] primitive-doc-bytes=${slicedBytes} mode
|
|
450
|
-
);
|
|
451
|
-
systemPromptOverride = harness.how.systemPrompt.replace(
|
|
452
|
-
PRIMITIVES_DOCUMENTATION,
|
|
453
|
-
sliced,
|
|
504
|
+
`[simple] primitive-doc-bytes=${slicedBytes} mode=${mode} baseline=${formatBytes} drop=${((1 - slicedBytes / formatBytes) * 100).toFixed(1)}% allowlist=${allowlist.length}/${allowlist.join(",")}${excludeTag}`
|
|
454
505
|
);
|
|
455
506
|
} else {
|
|
456
507
|
console.log(
|
|
457
|
-
`[simple] primitive-doc-bytes=${
|
|
508
|
+
`[simple] primitive-doc-bytes=${formatBytes} mode=full baseline=${formatBytes} drop=0.0%`
|
|
458
509
|
);
|
|
459
510
|
}
|
|
460
511
|
|
|
461
512
|
// ── Tool-driven primitive docs ──
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
// mutually exclusive in practice via profile selection; this
|
|
466
|
-
// ordering just defines the tiebreak.
|
|
467
|
-
const indexMode = resolvedPolicy.context.primitiveIndex ?? "off";
|
|
513
|
+
// A name-only index replaces whatever is left (it is the more
|
|
514
|
+
// aggressive cut). The two are mutually exclusive in practice via
|
|
515
|
+
// profile selection; this ordering just defines the tiebreak.
|
|
468
516
|
if (indexMode !== "off") {
|
|
469
517
|
const { buildPrimitiveIndex } = await import("../harness/primitive-index.js");
|
|
470
|
-
|
|
471
|
-
const indexBytes = Buffer.byteLength(
|
|
518
|
+
doc = buildPrimitiveIndex(PRIMITIVES_DOCUMENTATION, indexMode);
|
|
519
|
+
const indexBytes = Buffer.byteLength(doc, "utf8");
|
|
472
520
|
console.log(
|
|
473
|
-
`[simple] primitive-index-bytes=${indexBytes} mode=${indexMode} baseline=${fullBytes} drop=${((1 - indexBytes / fullBytes) * 100).toFixed(1)}
|
|
474
|
-
);
|
|
475
|
-
systemPromptOverride = (systemPromptOverride ?? harness.how.systemPrompt).replace(
|
|
476
|
-
PRIMITIVES_DOCUMENTATION,
|
|
477
|
-
index,
|
|
521
|
+
`[simple] primitive-index-bytes=${indexBytes} mode=${indexMode} baseline=${fullBytes} drop=${((1 - indexBytes / fullBytes) * 100).toFixed(1)}%`
|
|
478
522
|
);
|
|
479
523
|
}
|
|
480
524
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
// version (~55% smaller, same info). Mutually exclusive with index
|
|
484
|
-
// modes.
|
|
485
|
-
const docFormat = resolvedPolicy.context.primitiveDocFormat ?? "markdown";
|
|
486
|
-
if (docFormat === "ts" && indexMode === "off") {
|
|
487
|
-
const { PRIMITIVES_DOCUMENTATION_TS } = await import("../tools/get-primitives-ts.js");
|
|
488
|
-
const tsBytes = Buffer.byteLength(PRIMITIVES_DOCUMENTATION_TS, "utf8");
|
|
489
|
-
console.log(
|
|
490
|
-
`[simple] primitive-doc-ts-bytes=${tsBytes} baseline=${fullBytes} drop=${((1 - tsBytes / fullBytes) * 100).toFixed(1)}%`,
|
|
491
|
-
);
|
|
492
|
-
systemPromptOverride = (systemPromptOverride ?? harness.how.systemPrompt).replace(
|
|
493
|
-
PRIMITIVES_DOCUMENTATION,
|
|
494
|
-
PRIMITIVES_DOCUMENTATION_TS,
|
|
495
|
-
);
|
|
525
|
+
if (doc !== PRIMITIVES_DOCUMENTATION) {
|
|
526
|
+
systemPromptOverride = harness.how.systemPrompt.replace(PRIMITIVES_DOCUMENTATION, doc);
|
|
496
527
|
}
|
|
528
|
+
const sentPrompt = systemPromptOverride ?? harness.how.systemPrompt;
|
|
529
|
+
console.log(
|
|
530
|
+
`[simple] system-prompt-bytes=${Buffer.byteLength(sentPrompt, "utf8")} doc-format=${docFormat === "ts" && indexMode === "off" ? "ts" : docFormat} doc-bytes=${Buffer.byteLength(doc, "utf8")} mode=${indexMode !== "off" ? `index:${indexMode}` : mode}`
|
|
531
|
+
);
|
|
497
532
|
|
|
498
533
|
// ── plan→impl pipeline augmentation ──
|
|
499
534
|
// Append a 2-phase flow description when planFirstTurn is active so
|
|
@@ -548,7 +583,7 @@ export async function dispatchGeneration(
|
|
|
548
583
|
console.error(
|
|
549
584
|
`[dispatch] runHarness terminated: ${runResult.reason} ` +
|
|
550
585
|
`(source=${runResult.finalSource ? runResult.finalSource.length + "B" : "null"}, ` +
|
|
551
|
-
`compiled=${runResult.finalCompiled ? runResult.finalCompiled.length + "B" : "null"})
|
|
586
|
+
`compiled=${runResult.finalCompiled ? runResult.finalCompiled.length + "B" : "null"})`
|
|
552
587
|
);
|
|
553
588
|
}
|
|
554
589
|
|
|
@@ -560,7 +595,11 @@ export async function dispatchGeneration(
|
|
|
560
595
|
// `assembleGenerationResult` is async — the spread below must see the
|
|
561
596
|
// RESOLVED result, never the promise (a spread promise is `{}`: every
|
|
562
597
|
// consumer downstream lost `compiledCode` / `tokens` on 2026-09-09).
|
|
563
|
-
const result: GenerationResult = await assembleGenerationResult({
|
|
598
|
+
const result: GenerationResult = await assembleGenerationResult({
|
|
599
|
+
session,
|
|
600
|
+
telemetry,
|
|
601
|
+
source: finalSource,
|
|
602
|
+
});
|
|
564
603
|
// Record the arm this generation ran under (bench reports read it per
|
|
565
604
|
// cell). The canvas is the one the free prompt stated: explicit when
|
|
566
605
|
// passed, else the same shell × screen derivation the prompt used.
|
|
@@ -579,11 +618,13 @@ export async function dispatchGeneration(
|
|
|
579
618
|
*/
|
|
580
619
|
function withArmRecord(
|
|
581
620
|
result: GenerationResult,
|
|
582
|
-
params: Pick<GenerationDispatchParams, "designMode" | "canvas" | "shellType" | "screen"
|
|
621
|
+
params: Pick<GenerationDispatchParams, "designMode" | "canvas" | "shellType" | "screen">
|
|
583
622
|
): GenerationResult {
|
|
584
623
|
const recordedCanvas: CanvasClass | undefined =
|
|
585
624
|
params.canvas ??
|
|
586
|
-
(params.designMode === "free"
|
|
625
|
+
(params.designMode === "free"
|
|
626
|
+
? canvasForRendering(params.shellType, params.screen)
|
|
627
|
+
: undefined);
|
|
587
628
|
return {
|
|
588
629
|
...result,
|
|
589
630
|
...(params.designMode !== undefined ? { designMode: params.designMode } : {}),
|
package/src/check/run-tier0.ts
CHANGED
|
@@ -295,6 +295,85 @@ function detectCertainDoubleWiredActions(
|
|
|
295
295
|
* fallback ban it paints nothing.
|
|
296
296
|
*/
|
|
297
297
|
const MANIFEST_TOKENS: ReadonlySet<string> = new Set(consumedTokenManifest);
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Role names retired by ggui#987 that generated code still invents, each
|
|
301
|
+
* with the manifest name that replaced it (ggui#1325). The self-check names
|
|
302
|
+
* the successor, so the model does not spend a turn guessing another name.
|
|
303
|
+
*/
|
|
304
|
+
const RETIRED_TOKEN_SUCCESSORS: ReadonlyMap<string, string> = new Map([
|
|
305
|
+
['--ggui-color-surface', '--ggui-color-container'],
|
|
306
|
+
['--ggui-color-onSurface', '--ggui-color-onContainer'],
|
|
307
|
+
['--ggui-color-surfaceVariant', '--ggui-color-sunken'],
|
|
308
|
+
['--ggui-color-onSurfaceVariant', '--ggui-color-onSunken'],
|
|
309
|
+
]);
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* The manifest names nearest an invented one (ggui#1325): a retired role's
|
|
313
|
+
* successor when there is one; otherwise the names sharing the invented
|
|
314
|
+
* name's longest family prefix at a segment boundary
|
|
315
|
+
* (`--ggui-motion-duration-normal` → fast, base, slow); otherwise the names
|
|
316
|
+
* sharing any of its segments of four letters or more
|
|
317
|
+
* (`--ggui-duration-slow` → the motion durations). Empty when nothing is
|
|
318
|
+
* near, so the hint falls back to the manifest's general shape.
|
|
319
|
+
*/
|
|
320
|
+
export function suggestManifestTokens(name: string): readonly string[] {
|
|
321
|
+
const successor = RETIRED_TOKEN_SUCCESSORS.get(name);
|
|
322
|
+
if (successor !== undefined) return [successor];
|
|
323
|
+
// Family prefixes at the name's own segment boundaries, longest first: for
|
|
324
|
+
// `--ggui-motion-duration-normal` that is `--ggui-motion-duration-`, then
|
|
325
|
+
// `--ggui-motion-`; an empty-suffix name such as `--ggui-color-` (the model
|
|
326
|
+
// stopped mid-name) is its own longest prefix, so it gets the colour family.
|
|
327
|
+
const segments = name.split('-').filter((segment) => segment.length > 0);
|
|
328
|
+
const own = name.endsWith('-') ? segments.length : segments.length - 1;
|
|
329
|
+
for (let n = own; n >= 2; n--) {
|
|
330
|
+
const prefix = `--${segments.slice(0, n).join('-')}-`;
|
|
331
|
+
const family = consumedTokenManifest.filter((token) => token.startsWith(prefix));
|
|
332
|
+
if (family.length > 0) return wordHitsFirst(name, rolesFirst(family));
|
|
333
|
+
}
|
|
334
|
+
const words = new Set(
|
|
335
|
+
segments
|
|
336
|
+
.slice(1)
|
|
337
|
+
.filter((segment) => segment.length >= 4)
|
|
338
|
+
.map((segment) => segment.toLowerCase()),
|
|
339
|
+
);
|
|
340
|
+
return rolesFirst(
|
|
341
|
+
consumedTokenManifest.filter((token) =>
|
|
342
|
+
token.split('-').some((segment) => words.has(segment.toLowerCase())),
|
|
343
|
+
),
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Names that carry the invented name's last word lead the list: `--ggui-color-inverse` names `inverseLink` and
|
|
349
|
+
* `inverseOutline` before the rest of the colour family, since those are what the model was reaching for. The word is
|
|
350
|
+
* the last segment when it has four letters or more; shorter or empty suffixes leave the order alone.
|
|
351
|
+
*/
|
|
352
|
+
function wordHitsFirst(name: string, family: readonly string[]): readonly string[] {
|
|
353
|
+
const word = name.slice(name.lastIndexOf('-') + 1).toLowerCase();
|
|
354
|
+
if (word.length < 4) return family;
|
|
355
|
+
const hit = (token: string): boolean => token.slice(token.lastIndexOf('-') + 1).toLowerCase().includes(word);
|
|
356
|
+
return [...family.filter(hit), ...family.filter((token) => !hit(token))];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Role names (a non-numeric last segment) before ramp steps, so the taught vocabulary leads a long family. */
|
|
360
|
+
function rolesFirst(tokens: readonly string[]): readonly string[] {
|
|
361
|
+
const isRole = (token: string): boolean => !/^\d+$/.test(token.slice(token.lastIndexOf('-') + 1));
|
|
362
|
+
return [...tokens.filter(isRole), ...tokens.filter((token) => !isRole(token))];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** The sentence that names the real token first (ggui#1325); empty when nothing in the manifest is near. */
|
|
366
|
+
function offManifestHint(name: string): string {
|
|
367
|
+
const successor = RETIRED_TOKEN_SUCCESSORS.get(name);
|
|
368
|
+
if (successor !== undefined) {
|
|
369
|
+
return `"${name}" was retired (ggui#987); the current name is var(${successor}). `;
|
|
370
|
+
}
|
|
371
|
+
const near = suggestManifestTokens(name);
|
|
372
|
+
if (near.length === 0) return '';
|
|
373
|
+
const shown = near.slice(0, 8).map((token) => `var(${token})`).join(', ');
|
|
374
|
+
const more = near.length > 8 ? ` (and ${near.length - 8} more in that family)` : '';
|
|
375
|
+
return `Nearest names in the manifest: ${shown}${more}. `;
|
|
376
|
+
}
|
|
298
377
|
const TOKEN_REF_RE = /var\(\s*(--ggui-[a-zA-Z0-9-]+)/g;
|
|
299
378
|
|
|
300
379
|
/** Map wire kind to the matching `@ggui-ai/wire` hook name. */
|
|
@@ -790,7 +869,7 @@ export async function runTier0Checks(
|
|
|
790
869
|
subcategory: 'off-manifest-token',
|
|
791
870
|
severity: 'critical',
|
|
792
871
|
description: `Token "${name}" is not in the design system's token manifest — no theme defines it, so it renders unset.`,
|
|
793
|
-
fix:
|
|
872
|
+
fix: `${offManifestHint(name)}Choose a token that exists: the closed vocabulary is the manifest the design-system docs list (colors: var(--ggui-color-primary-500), var(--ggui-color-onContainer), …; spacing: var(--ggui-spacing-4); type: var(--ggui-font-size-sm)). Never invent a name and never add a literal fallback.`,
|
|
794
873
|
line: lineNum,
|
|
795
874
|
});
|
|
796
875
|
}
|