@ggui-ai/ui-gen 0.9.0 → 0.10.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/adapters/generation-dispatch.d.ts +9 -2
- package/dist/adapters/generation-dispatch.js +547 -85
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +28 -11
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +5 -3
- package/dist/advanced/index.js +563 -92
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +109 -3
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +14 -2
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +11 -1
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-B9kV4U9h.d.ts → create-ui-generator-CF0EL0Li.d.ts} +13 -1
- package/dist/{dispatch-Deo9qzrG.d.ts → dispatch-D75M-OfZ.d.ts} +1 -1
- package/dist/evaluation/axis-checks/index.d.ts +3 -3
- package/dist/evaluation/axis-checks/index.js +101 -0
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +1 -1
- package/dist/evaluation/axis-checks/registry.js +101 -0
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +46 -3
- package/dist/evaluation/index.js +358 -17
- package/dist/evaluation/index.js.map +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +2 -2
- package/dist/harness/check/runtime-render/index.js +110 -3
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/index.d.ts +2 -2
- package/dist/harness/index.js +263 -18
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.d.ts +1 -1
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.js +17 -3
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +1 -1
- package/dist/harness/runtime.d.ts +9 -2
- package/dist/harness/runtime.js +20 -6
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +1 -1
- package/dist/harness/types-public.js +14 -2
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +563 -92
- package/dist/index.js.map +1 -1
- package/dist/{llm-router-1TyI0_TV.d.ts → llm-router-BEVtbAb9.d.ts} +61 -4
- package/dist/patch.d.ts +1 -1
- package/dist/provider-adapter.d.ts +16 -6
- package/dist/provider-adapter.js.map +1 -1
- package/dist/providers/index.d.ts +19 -6
- package/dist/providers/index.js.map +1 -1
- package/dist/{render-check-CNZjHD5g.d.ts → render-check-BNYGVDJd.d.ts} +1 -1
- package/dist/{types-public-C36k1q6s.d.ts → types-public-DIpjC-OA.d.ts} +1 -1
- package/dist/workflows.d.ts +1 -1
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +9 -0
- package/src/adapters/openrouter/client.ts +2 -2
- package/src/adapters/openrouter/types.ts +10 -0
- package/src/boilerplate/generate.ts +10 -2
- package/src/boilerplate/json-schema-ts.ts +22 -2
- package/src/boilerplate/system-prompt.ts +1 -0
- package/src/compile.ts +1 -1
- package/src/contract-context.ts +10 -2
- package/src/create-ui-generator.ts +14 -0
- package/src/design-system-docs.ts +6 -4
- package/src/evaluation/axis-checks/checks/state-ui-affordance.ts +162 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/index.ts +11 -0
- package/src/evaluation/llm-evaluator.ts +14 -1
- package/src/evaluation/visual-evaluator.ts +44 -65
- package/src/harness/check/runtime-render/adapter.ts +18 -0
- package/src/harness/check/runtime-render/index.ts +8 -0
- package/src/harness/check/runtime-render/prepare-mockup.ts +14 -0
- package/src/harness/check/runtime-render/production-act-shim.ts +158 -0
- package/src/harness/check/runtime-render/render-check.ts +72 -1
- package/src/harness/coding/run-eval-round.ts +61 -5
- package/src/harness/index.ts +1 -1
- package/src/harness/llm-router.ts +397 -18
- package/src/harness/llm-trace-sink.ts +2 -1
- package/src/harness/runtime.ts +7 -0
- package/src/index.ts +5 -5
- package/src/provider-adapter.ts +16 -6
- package/src/providers/bedrock.ts +5 -4
- package/src/providers/index.ts +16 -4
- package/src/tools/render-check.ts +19 -7
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// packages/ui-gen/src/harness/check/runtime-render/production-act-shim.ts
|
|
2
|
+
//
|
|
3
|
+
// Minimal adaptation to two upstream constraints, neither of which
|
|
4
|
+
// this package controls:
|
|
5
|
+
//
|
|
6
|
+
// 1. React intentionally strips `act` from its PRODUCTION build.
|
|
7
|
+
// `react/cjs/react.production.js` does not export it — only the
|
|
8
|
+
// development build does (`typeof require('react').act` is
|
|
9
|
+
// `'function'` under a dev build, `undefined` under production).
|
|
10
|
+
// 2. `@testing-library/react`'s `act-compat` module hard-requires
|
|
11
|
+
// `act` at MODULE SCOPE: it snapshots
|
|
12
|
+
// `typeof React.act === 'function' ? React.act : DeprecatedReactTestUtils.act`
|
|
13
|
+
// into a `const` the first time it is imported in the process.
|
|
14
|
+
// Under a production build, that snapshot resolves to React
|
|
15
|
+
// DOM's `react-dom/test-utils` production `act` stub, which
|
|
16
|
+
// THROWS `React.act is not a function` the moment it is called.
|
|
17
|
+
//
|
|
18
|
+
// Hosts that run this probe inside a production Node process
|
|
19
|
+
// (server-side blueprint validation) hit this: every render throws
|
|
20
|
+
// through RTL's `act` wrapper before the probe ever sees the
|
|
21
|
+
// component. Hosts that run it under a development/test build (every
|
|
22
|
+
// CI/vitest run in this repo — `NODE_ENV=test` resolves the dev
|
|
23
|
+
// build) never see it; `act` is real there and this shim is a no-op.
|
|
24
|
+
//
|
|
25
|
+
// **Load-order constraint**: this MUST run before the FIRST import of
|
|
26
|
+
// `@testing-library/react` anywhere in the process — act-compat binds
|
|
27
|
+
// its `act` reference into a module-scope `const` at import time, so
|
|
28
|
+
// patching `react-dom/test-utils` AFTER that import has already
|
|
29
|
+
// happened does nothing (the snapshot already captured the broken
|
|
30
|
+
// stub). Patch `react-dom/test-utils`, never `react` itself — that
|
|
31
|
+
// entry point is deprecated and test-only, so this patch cannot
|
|
32
|
+
// perturb any production rendering path; nothing else in a server
|
|
33
|
+
// process legitimately imports it.
|
|
34
|
+
//
|
|
35
|
+
// `createRequire` (not `import`) reaches these modules deliberately:
|
|
36
|
+
// ESM namespace objects are frozen, so a normal `import` gives no way
|
|
37
|
+
// to mutate `act` on it. The CJS `module.exports` objects `require`
|
|
38
|
+
// resolves are mutable, and RTL's act-compat interop-copies THAT
|
|
39
|
+
// object's properties at its own load time — so mutating it here,
|
|
40
|
+
// before RTL loads, is what makes the patch visible to RTL at all.
|
|
41
|
+
|
|
42
|
+
import { createRequire } from "node:module";
|
|
43
|
+
|
|
44
|
+
let installed = false;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The one property this module reads/mutates on `react` and
|
|
48
|
+
* `react-dom/test-utils`'s CJS exports. A minimal local shape rather
|
|
49
|
+
* than the real (deprecated, possibly absent) type declarations for
|
|
50
|
+
* `react-dom/test-utils` — narrow enough to type-check the mutation
|
|
51
|
+
* without `as any` / `Record<string, unknown>`.
|
|
52
|
+
*/
|
|
53
|
+
interface MutableActCarrier {
|
|
54
|
+
act?: unknown;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The one React DOM export this module needs — typed narrowly for the same reason. */
|
|
58
|
+
interface FlushSyncCarrier {
|
|
59
|
+
flushSync: (fn: () => void) => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Patch `react-dom/test-utils`'s `act` with a `flushSync`-backed
|
|
64
|
+
* replacement, but ONLY when the current process is running React's
|
|
65
|
+
* production build (no real `act` exported). Idempotent — safe to
|
|
66
|
+
* call more than once or from more than one call site; the actual
|
|
67
|
+
* patch only ever applies once per process.
|
|
68
|
+
*
|
|
69
|
+
* No-op (by design, not by accident) whenever `react`'s own `act`
|
|
70
|
+
* export is a real function — every dev/test build, including every
|
|
71
|
+
* vitest run in this repo. This shim changes zero behavior in CI.
|
|
72
|
+
*/
|
|
73
|
+
export function installProductionActShim(): void {
|
|
74
|
+
if (installed) return;
|
|
75
|
+
installed = true;
|
|
76
|
+
|
|
77
|
+
const require_ = createRequire(import.meta.url);
|
|
78
|
+
|
|
79
|
+
let reactCjs: MutableActCarrier;
|
|
80
|
+
try {
|
|
81
|
+
reactCjs = require_("react");
|
|
82
|
+
} catch {
|
|
83
|
+
// React itself isn't resolvable in this process — the probe
|
|
84
|
+
// surfaces its own load error; nothing for this shim to patch.
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof reactCjs.act === "function") {
|
|
89
|
+
// Dev/test builds export a real `act` — strict no-op.
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const testUtils: MutableActCarrier = require_("react-dom/test-utils");
|
|
95
|
+
const reactDom: FlushSyncCarrier = require_("react-dom");
|
|
96
|
+
testUtils.act = createFlushSyncAct(reactDom.flushSync);
|
|
97
|
+
} catch {
|
|
98
|
+
// Frozen/locked exports (or a react-dom build without flushSync)
|
|
99
|
+
// — leave it. The probe then surfaces the raw upstream error,
|
|
100
|
+
// which is the pre-fix behavior: an honest failure, not a
|
|
101
|
+
// silently-broken patch.
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build an `act`-compatible replacement backed by `flushSync`, which
|
|
107
|
+
* — unlike `act` — DOES exist in React's production build.
|
|
108
|
+
*
|
|
109
|
+
* `flushSync` is the single property this shim relies on from dev
|
|
110
|
+
* `act`: it forces the scheduled render to commit SYNCHRONOUSLY
|
|
111
|
+
* before returning, so a render throw propagates synchronously to the
|
|
112
|
+
* caller and the DOM is fully committed by the time this resolves.
|
|
113
|
+
*
|
|
114
|
+
* Returned function is act-style: if the callback itself returned a
|
|
115
|
+
* thenable (RTL's async act-wrapped interactions), the result
|
|
116
|
+
* resolves once that thenable settles (rejecting on its rejection);
|
|
117
|
+
* otherwise it resolves after one microtask, mirroring `act`'s own
|
|
118
|
+
* "flush the microtask queue" contract for the synchronous case.
|
|
119
|
+
*
|
|
120
|
+
* **Known, documented delta vs. a real dev `act`**: passive effects
|
|
121
|
+
* (`useEffect`) flush on a LATER task under `flushSync`, not
|
|
122
|
+
* synchronously at act-exit — `flushSync` only guarantees the
|
|
123
|
+
* synchronous commit, not effect flushing. The probe's uncaught-error
|
|
124
|
+
* handlers plus its 100ms teardown grace window (`render-check.ts`'s
|
|
125
|
+
* `cleanupAsyncInfra`) already cover a late effect throwing after this
|
|
126
|
+
* resolves, so this delta does not reopen the crash class the probe
|
|
127
|
+
* exists to catch.
|
|
128
|
+
*
|
|
129
|
+
* Exported separately so the flushSync/thenable semantics are unit
|
|
130
|
+
* testable without a real React runtime.
|
|
131
|
+
*/
|
|
132
|
+
export function createFlushSyncAct(
|
|
133
|
+
flushSync: (fn: () => void) => void,
|
|
134
|
+
): (callback: () => unknown) => Promise<void> {
|
|
135
|
+
return (callback: () => unknown): Promise<void> => {
|
|
136
|
+
let callbackResult: unknown;
|
|
137
|
+
flushSync(() => {
|
|
138
|
+
callbackResult = callback();
|
|
139
|
+
});
|
|
140
|
+
if (isThenable(callbackResult)) {
|
|
141
|
+
const pending = callbackResult;
|
|
142
|
+
return new Promise<void>((resolve, reject) => {
|
|
143
|
+
pending.then(() => resolve(), reject);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return new Promise<void>((resolve) => {
|
|
147
|
+
queueMicrotask(resolve);
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
153
|
+
return (
|
|
154
|
+
typeof value === "object" &&
|
|
155
|
+
value !== null &&
|
|
156
|
+
typeof (value as { then?: unknown }).then === "function"
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -24,6 +24,7 @@ import { HOOK_NAME_RE, listContractGadgets } from "@ggui-ai/protocol";
|
|
|
24
24
|
import { createProbe, createProbeWireConfig, type Probe } from "./probe.js";
|
|
25
25
|
import { loadComponent } from "./load-component.js";
|
|
26
26
|
import { findWiring, type WiringDetection } from "./find-wiring.js";
|
|
27
|
+
import { installProductionActShim } from "./production-act-shim.js";
|
|
27
28
|
// All happy-dom / RTL / user-event narrowing flows through the typed
|
|
28
29
|
// host boundary (one set of structural interfaces + validating guards;
|
|
29
30
|
// DOM-lib-free — some downstream packages, e.g. cloud/amplify, exclude
|
|
@@ -70,7 +71,8 @@ export interface RenderCheckIssue {
|
|
|
70
71
|
| "render-no-throw"
|
|
71
72
|
| "action-wiring"
|
|
72
73
|
| "prop-coverage"
|
|
73
|
-
| "stream-rerender"
|
|
74
|
+
| "stream-rerender"
|
|
75
|
+
| "optional-props-omitted";
|
|
74
76
|
readonly outcome: CheckOutcome;
|
|
75
77
|
readonly subject?: string;
|
|
76
78
|
readonly reason: string;
|
|
@@ -237,6 +239,9 @@ export async function runRenderCheck(
|
|
|
237
239
|
// internals.
|
|
238
240
|
uninstallSpy = probe.installPostMessageSpy();
|
|
239
241
|
|
|
242
|
+
// Must run before RTL's first import in this process — see
|
|
243
|
+
// production-act-shim.ts's docstring for why.
|
|
244
|
+
installProductionActShim();
|
|
240
245
|
const { render, cleanup } = await import("@testing-library/react");
|
|
241
246
|
// user-event v14 default export — see https://testing-library.com/docs/user-event/intro/
|
|
242
247
|
const userEventModule = await import("@testing-library/user-event");
|
|
@@ -514,6 +519,72 @@ export async function runRenderCheck(
|
|
|
514
519
|
if (issue) issues.push(issue);
|
|
515
520
|
}
|
|
516
521
|
}
|
|
522
|
+
|
|
523
|
+
// ── Check 7: Optional-props omission (BLOCK on crash, ggui#528) ──
|
|
524
|
+
// The main pass renders with EVERY declared prop filled, so a
|
|
525
|
+
// component that assumes an optional prop is present always
|
|
526
|
+
// passes here and crashes only live, when an agent legitimately
|
|
527
|
+
// omits it (the wire gate requires only `required === true`
|
|
528
|
+
// props). Render a second instance with the optional props
|
|
529
|
+
// STRIPPED — a throw is exactly the latent-undefined class this
|
|
530
|
+
// check exists to surface. Runs only when the contract declares
|
|
531
|
+
// at least one optional prop that the mockup filled; zero cost
|
|
532
|
+
// otherwise.
|
|
533
|
+
const declaredProps = input.contract?.propsSpec?.properties;
|
|
534
|
+
if (declaredProps) {
|
|
535
|
+
const optionalFilled = Object.entries(declaredProps)
|
|
536
|
+
.filter(([name, entry]) =>
|
|
537
|
+
(entry as { required?: boolean }).required !== true &&
|
|
538
|
+
input.mockupProps[name] !== undefined,
|
|
539
|
+
)
|
|
540
|
+
.map(([name]) => name);
|
|
541
|
+
if (optionalFilled.length > 0) {
|
|
542
|
+
const requiredOnlyProps: JsonObject = { ...input.mockupProps };
|
|
543
|
+
for (const name of optionalFilled) delete requiredOnlyProps[name];
|
|
544
|
+
const omitBoundaryRef: { error?: Error } = {};
|
|
545
|
+
class OmitErrorBoundary extends React.Component<
|
|
546
|
+
{ children?: React.ReactNode },
|
|
547
|
+
{ caught: boolean }
|
|
548
|
+
> {
|
|
549
|
+
state = { caught: false };
|
|
550
|
+
static getDerivedStateFromError(): { caught: boolean } {
|
|
551
|
+
return { caught: true };
|
|
552
|
+
}
|
|
553
|
+
componentDidCatch(error: Error): void {
|
|
554
|
+
omitBoundaryRef.error ??= error;
|
|
555
|
+
}
|
|
556
|
+
render(): React.ReactNode {
|
|
557
|
+
if (this.state.caught) return null;
|
|
558
|
+
return this.props.children;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
render(
|
|
563
|
+
React.createElement(GguiWireProvider, {
|
|
564
|
+
config: wireConfig,
|
|
565
|
+
children: React.createElement(OmitErrorBoundary, {
|
|
566
|
+
children: React.createElement(Component, requiredOnlyProps),
|
|
567
|
+
}),
|
|
568
|
+
}),
|
|
569
|
+
);
|
|
570
|
+
} catch (e) {
|
|
571
|
+
omitBoundaryRef.error ??=
|
|
572
|
+
e instanceof Error ? e : new Error(String(e));
|
|
573
|
+
}
|
|
574
|
+
if (omitBoundaryRef.error) {
|
|
575
|
+
issues.push({
|
|
576
|
+
check: "optional-props-omitted",
|
|
577
|
+
outcome: "failed",
|
|
578
|
+
subject: optionalFilled.join(", "),
|
|
579
|
+
reason:
|
|
580
|
+
`Render crashed when the optional prop(s) ${optionalFilled
|
|
581
|
+
.map((n) => `'${n}'`)
|
|
582
|
+
.join(", ")} were omitted: ${omitBoundaryRef.error.message}. ` +
|
|
583
|
+
`The contract marks them optional (no \`required: true\`), so a legitimate render may leave them out.`,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
517
588
|
} finally {
|
|
518
589
|
cleanup();
|
|
519
590
|
}
|
|
@@ -121,6 +121,29 @@ export interface EvalRoundResult {
|
|
|
121
121
|
/** Cap feedback to N issues per eval-fix turn so the LLM stays surgical. */
|
|
122
122
|
const MAX_FEEDBACK_ISSUES = 3;
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Pick the ≤{@link MAX_FEEDBACK_ISSUES} issues an eval-fix turn feeds
|
|
126
|
+
* back. Provenance-aware (Exp 54 P1 addendum): ONE slot is reserved
|
|
127
|
+
* for the first deterministic tier-0 fail (axis/mode checks —
|
|
128
|
+
* machine-verified findings) when one exists; the rest fill
|
|
129
|
+
* fails-first then warns. Without the reservation, the merge order
|
|
130
|
+
* put deterministic findings last and any round with ≥3 LLM-judge
|
|
131
|
+
* fails silently starved them — the model never saw a precision-1.00
|
|
132
|
+
* structural finding while the stuck detector blamed it for not
|
|
133
|
+
* fixing it. Exactly one slot, so crash-class LLM fails keep the
|
|
134
|
+
* other two. Exported for the pins.
|
|
135
|
+
*/
|
|
136
|
+
export function selectActionableFeedback(issues: readonly EvalIssue[]): EvalIssue[] {
|
|
137
|
+
const pool = issues.filter((i) => i.result === "fail" || i.result === "warn");
|
|
138
|
+
const reserved = pool.find((i) => i.tier === 0 && i.result === "fail");
|
|
139
|
+
return [
|
|
140
|
+
...(reserved ? [reserved] : []),
|
|
141
|
+
...pool
|
|
142
|
+
.filter((i) => i !== reserved)
|
|
143
|
+
.sort((a, b) => (a.result === "fail" ? 0 : 1) - (b.result === "fail" ? 0 : 1)),
|
|
144
|
+
].slice(0, MAX_FEEDBACK_ISSUES);
|
|
145
|
+
}
|
|
146
|
+
|
|
124
147
|
/**
|
|
125
148
|
* Fingerprint a fail issue for cross-round stuck detection. Identifies the
|
|
126
149
|
* "same fail" without being thrown by minor description-text variation.
|
|
@@ -424,6 +447,13 @@ export async function runEvalRound(
|
|
|
424
447
|
// inside the same concurrent generation, so the race is
|
|
425
448
|
// real here too, not just on the coding turn).
|
|
426
449
|
routeOverride: evaluationAgent.routeOverride,
|
|
450
|
+
// #489 — thread the same provider-429-retry observer.
|
|
451
|
+
// Without this, a rate-limited retry inside the
|
|
452
|
+
// evaluation agent's apiCall() happens (the base-class
|
|
453
|
+
// retry loop is unconditional) but never reaches the
|
|
454
|
+
// caller, silently missing the host application's
|
|
455
|
+
// structured log.
|
|
456
|
+
onRetry: evaluationAgent.onRetry,
|
|
427
457
|
},
|
|
428
458
|
preWarmedContext,
|
|
429
459
|
)
|
|
@@ -437,6 +467,14 @@ export async function runEvalRound(
|
|
|
437
467
|
passThreshold: visualThreshold,
|
|
438
468
|
sampleProps: visualEvaluation?.sampleProps,
|
|
439
469
|
viewport: visualEvaluation?.viewport,
|
|
470
|
+
// #484/#489 — same threading as the LLM-eval leg above:
|
|
471
|
+
// the visual eval's multimodal call runs inside the same
|
|
472
|
+
// concurrent generation, so it needs the same routing
|
|
473
|
+
// override (never fall back to `process.env`) and the
|
|
474
|
+
// same provider-429-retry observer (retries reach the
|
|
475
|
+
// host application's structured log).
|
|
476
|
+
routeOverride: visualEvalAgent.routeOverride,
|
|
477
|
+
onRetry: visualEvalAgent.onRetry,
|
|
440
478
|
},
|
|
441
479
|
)
|
|
442
480
|
: null,
|
|
@@ -758,10 +796,19 @@ export async function runEvalRound(
|
|
|
758
796
|
// Feeding too many issues at once causes the LLM to make many simultaneous
|
|
759
797
|
// changes, breaking JSX nesting in complex components. Prioritize fails
|
|
760
798
|
// first, then warns, capped to keep fixes surgical.
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
799
|
+
//
|
|
800
|
+
// Provenance-aware selection (Exp 54 P1 addendum): deterministic
|
|
801
|
+
// tier-0 fails (axis/mode checks — machine-verified, precision-1.00
|
|
802
|
+
// findings) get ONE reserved slot, first. Before this, the merge
|
|
803
|
+
// order (`allIssues = [...llm, ...visual, ...mode]`) + stable sort
|
|
804
|
+
// + slice meant any round where the LLM leg emitted ≥3 fails
|
|
805
|
+
// silently starved every deterministic finding — the model never
|
|
806
|
+
// saw it, the fingerprint set still recorded it, and the stuck
|
|
807
|
+
// detector exited on an issue that was never delivered (2/9 cells
|
|
808
|
+
// shipped a P0 structural defect this way, one displaced by
|
|
809
|
+
// design-token nits). Exactly one slot is reserved so crash-class
|
|
810
|
+
// LLM fails keep the other two.
|
|
811
|
+
const allActionable = selectActionableFeedback(evalResult.issues);
|
|
765
812
|
const issueLines = allActionable.map((issue) => {
|
|
766
813
|
const tag = issue.result === "fail" ? "FAIL" : "WARN";
|
|
767
814
|
return `[${tag}] ${issue.category}${issue.subcategory ? "/" + issue.subcategory : ""}: ${issue.description}\n Fix: ${issue.fix}`;
|
|
@@ -777,7 +824,16 @@ export async function runEvalRound(
|
|
|
777
824
|
evalResult,
|
|
778
825
|
evalRoundsUsed,
|
|
779
826
|
prevModeSubcats: updatedPrevModeSubcats,
|
|
780
|
-
|
|
827
|
+
// DELIVERED fails only (Exp 54 P1 addendum, proposal 2): the
|
|
828
|
+
// stuck detector's premise is "the LLM saw these and did not
|
|
829
|
+
// resolve them" — recording fails that the MAX_FEEDBACK_ISSUES
|
|
830
|
+
// cap cut from the feedback turns truncation into a premature
|
|
831
|
+
// stuck-exit (the model never saw the issue it is blamed for
|
|
832
|
+
// not fixing). An undelivered fail recurring next round now
|
|
833
|
+
// reads as NEW, gets delivered, and earns its fix round.
|
|
834
|
+
prevFailFingerprints: new Set(
|
|
835
|
+
allActionable.filter((i) => i.result === "fail").map(fingerprintFail),
|
|
836
|
+
),
|
|
781
837
|
preWarmedContext,
|
|
782
838
|
evalTokens,
|
|
783
839
|
evalLlmMs,
|
package/src/harness/index.ts
CHANGED
|
@@ -27,7 +27,7 @@ export type {
|
|
|
27
27
|
} from "./types-public.js";
|
|
28
28
|
export { defaultApplyPatch, applyLineRanges } from "../patch.js";
|
|
29
29
|
export { callLLM, createAgent } from "./llm-router.js";
|
|
30
|
-
export type { AgentConfig, LLMResponse } from "./llm-router.js";
|
|
30
|
+
export type { AgentConfig, LLMResponse, ProviderRetryInfo } from "./llm-router.js";
|
|
31
31
|
export {
|
|
32
32
|
computeHarnessId,
|
|
33
33
|
computeHarnessName,
|