@mutagent/evaluator 0.1.0-alpha.4 → 0.1.0-alpha.6
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/.claude/skills/mutagent-evaluator/SKILL.md +6 -5
- package/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +76 -12
- package/.claude/skills/mutagent-evaluator/assets/code-quality-criteria.yaml +75 -0
- package/.claude/skills/mutagent-evaluator/lenses/methodology-critic-lens.md +1 -1
- package/.claude/skills/mutagent-evaluator/references/edd-loop.md +6 -6
- package/.claude/skills/mutagent-evaluator/references/eval-stage.md +36 -3
- package/.claude/skills/mutagent-evaluator/references/memory-format.md +1 -1
- package/.claude/skills/mutagent-evaluator/references/operation-inventory.md +1 -0
- package/.claude/skills/mutagent-evaluator/schemas/methodology-review.schema.yaml +1 -1
- package/.claude/skills/mutagent-evaluator/scripts/agent-dispatch.ts +0 -0
- package/.claude/skills/mutagent-evaluator/scripts/build-review-ui.ts +320 -6
- package/.claude/skills/mutagent-evaluator/scripts/cli/audit-run.ts +6 -3
- package/.claude/skills/mutagent-evaluator/scripts/cli/prep.ts +32 -2
- package/.claude/skills/mutagent-evaluator/scripts/cli/profile-subject.ts +1 -1
- package/.claude/skills/mutagent-evaluator/scripts/code-quality-verdict.ts +277 -0
- package/.claude/skills/mutagent-evaluator/scripts/config/schema.ts +3 -3
- package/.claude/skills/mutagent-evaluator/scripts/contracts/eval-matrix.ts +39 -0
- package/.claude/skills/mutagent-evaluator/scripts/determine-outcome.ts +8 -0
- package/.claude/skills/mutagent-evaluator/scripts/edd/change-request.ts +1 -1
- package/.claude/skills/mutagent-evaluator/scripts/edd/edd-types.ts +2 -2
- package/.claude/skills/mutagent-evaluator/scripts/judge-prompt-template.ts +144 -15
- package/.claude/skills/mutagent-evaluator/scripts/memory/append.ts +1 -1
- package/.claude/skills/mutagent-evaluator/scripts/memory/ratify.ts +165 -0
- package/.claude/skills/mutagent-evaluator/scripts/persist-eval-criteria.ts +232 -0
- package/.claude/skills/mutagent-evaluator/scripts/prep-tasks.ts +15 -4
- package/.claude/skills/mutagent-evaluator/scripts/read-manifest.ts +120 -0
- package/.claude/skills/mutagent-evaluator/scripts/read-unitf-traces.ts +34 -2
- package/.claude/skills/mutagent-evaluator/scripts/render-build-cards.ts +37 -0
- package/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +9 -91
- package/.claude/skills/mutagent-evaluator/scripts/report-fragments.ts +173 -0
- package/.claude/skills/mutagent-evaluator/scripts/route-failures.ts +14 -4
- package/.claude/skills/mutagent-evaluator/scripts/run-deterministic.ts +114 -18
- package/.claude/skills/mutagent-evaluator/scripts/run-pipeline.ts +39 -4
- package/.claude/skills/mutagent-evaluator/scripts/subject-profile.ts +82 -0
- package/.claude/skills/mutagent-evaluator/scripts/sync-eval-criteria.ts +244 -0
- package/.claude/skills/mutagent-evaluator/scripts/unitf-to-evaltrace.ts +64 -21
- package/bin/mutagent-cli.mjs +659 -8
- package/package.json +2 -2
|
@@ -83,6 +83,33 @@ export function renderBuildEvalsProgressCard(p: BuildEvalsProgress): string {
|
|
|
83
83
|
].join("\n");
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// ── EVAL-1 — "what this stage will produce" notice (up-front, at stage start) ─
|
|
87
|
+
export interface StageProducesNotice {
|
|
88
|
+
subject: string;
|
|
89
|
+
/** the command about to run (e.g. "*build-dataset" | "*build-evals" | "*eval"). */
|
|
90
|
+
command: string;
|
|
91
|
+
/** the named deliverables this stage emits (dataset · criteria · which files). */
|
|
92
|
+
produces: string[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* EVAL-1 (1) — render the up-front notice that names WHAT this stage will produce,
|
|
97
|
+
* so a build never runs silently with the operator unaware a dataset/criteria are
|
|
98
|
+
* coming. Emitted at Step 0, BEFORE the build. PURE. NOT an approval gate — a
|
|
99
|
+
* notification only (operator constraint).
|
|
100
|
+
*/
|
|
101
|
+
export function renderStageProducesNotice(n: StageProducesNotice): string {
|
|
102
|
+
const lines = [
|
|
103
|
+
TOP,
|
|
104
|
+
row(`▷ ${n.command} — ${n.subject}`),
|
|
105
|
+
MID,
|
|
106
|
+
row("this stage will produce:"),
|
|
107
|
+
];
|
|
108
|
+
for (const p of n.produces) lines.push(row(` • ${p}`));
|
|
109
|
+
lines.push(BOT);
|
|
110
|
+
return lines.join("\n");
|
|
111
|
+
}
|
|
112
|
+
|
|
86
113
|
// ── F22 — dataset entity card (verbose, after *build-dataset) ─────────────────
|
|
87
114
|
export interface DatasetEntity {
|
|
88
115
|
subject: string;
|
|
@@ -99,6 +126,12 @@ export function renderDatasetEntityCard(e: DatasetEntity): string {
|
|
|
99
126
|
MID,
|
|
100
127
|
row(`categories (${e.categories.length}) · ${e.totalItems} items total`),
|
|
101
128
|
];
|
|
129
|
+
// EVAL-1 — a build that produced NOTHING must WARN, never render a blank card that
|
|
130
|
+
// reads as a silent success (the dogfood symptom: a build that reported and a build
|
|
131
|
+
// that stayed silent looked identical).
|
|
132
|
+
if (e.categories.length === 0 || e.totalItems === 0) {
|
|
133
|
+
lines.push(row("⚠ EMPTY — this build produced no dataset items (nothing to evaluate)."));
|
|
134
|
+
}
|
|
102
135
|
for (const c of e.categories) {
|
|
103
136
|
lines.push(row(` • ${c.id}: ${c.items} items (edge: ${c.edgeItems})`));
|
|
104
137
|
}
|
|
@@ -129,6 +162,10 @@ export function renderEvalsEntityCard(e: EvalsEntity): string {
|
|
|
129
162
|
row(`output sink: ${e.outputSink}`),
|
|
130
163
|
row(`criteria (${e.criteria.length}):`),
|
|
131
164
|
];
|
|
165
|
+
// EVAL-1 — an eval suite with zero criteria is a silent no-op build; WARN loudly.
|
|
166
|
+
if (e.criteria.length === 0) {
|
|
167
|
+
lines.push(row("⚠ EMPTY — this build produced no criteria (the suite judges nothing)."));
|
|
168
|
+
}
|
|
132
169
|
for (const c of e.criteria) {
|
|
133
170
|
lines.push(row(` • ${c.id} [${c.severity}·${c.kind}]`));
|
|
134
171
|
}
|
|
@@ -69,6 +69,14 @@ import { assessEmitCompleteness, type EmitCompleteness } from "./emit-completene
|
|
|
69
69
|
import type { Scorecard } from "./evaluate.ts";
|
|
70
70
|
import type { SourceMap } from "./source-map.ts";
|
|
71
71
|
import { routeFailures, type FailureRef, type HandoverBundle } from "./route-failures.ts";
|
|
72
|
+
// EV-3 — the step<->criterion side-by-side render fns (`cvRefs` + `critiqueBlock`
|
|
73
|
+
// + `sideBySide`, the last carrying its local `refExaminesStep` + `covEntry`) are
|
|
74
|
+
// EXTRACTED VERBATIM into report-fragments.ts and spliced back into the client
|
|
75
|
+
// script IN PLACE below, so this report's emitted HTML is byte-identical to
|
|
76
|
+
// before the extraction. The review UI (`build-review-ui.ts`) imports the SAME
|
|
77
|
+
// constant to render the identical view. Byte-identity is guarded by this file's
|
|
78
|
+
// render tests + a before/after HTML diff.
|
|
79
|
+
import { SIDE_BY_SIDE_FRAGMENT_JS } from "./report-fragments.ts";
|
|
72
80
|
|
|
73
81
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
74
82
|
const BRAND_DIR = join(HERE, "..", "assets", "brand");
|
|
@@ -3319,97 +3327,7 @@ function render(){var qi=document.getElementById('q');if(!qi)return;var q=(qi.va
|
|
|
3319
3327
|
// critique-before-verdict + grounding refs, read off the verdict file's verdicts[].
|
|
3320
3328
|
// Shared by BOTH the side-by-side drill (walk traces) and the per-trajectory
|
|
3321
3329
|
// scorecard drill (no-walk traces) so NO judged trace renders an empty panel.
|
|
3322
|
-
|
|
3323
|
-
return '<div class="cvrefs">'+refs.map(function(rf){var s=(typeof rf==='string')?rf:[rf.obs,rf.path,rf.value].filter(Boolean).join(' · ');return s?'<span class="cvref">'+esc(s)+'</span>':'';}).join('')+'</div>';}
|
|
3324
|
-
function critiqueBlock(cvs){if(!cvs||!cvs.length)return '';
|
|
3325
|
-
var order={fail:0,uncertain:1,indeterminate:1,pass:2,na:3};
|
|
3326
|
-
var rows=cvs.slice().sort(function(a,b){return (order[a.result]==null?9:order[a.result])-(order[b.result]==null?9:order[b.result]);}).map(function(v){
|
|
3327
|
-
var res=v.result||'na';var disp=res==='uncertain'?'indeterminate':res;
|
|
3328
|
-
var conf=(v.confidence!=null)?'<span class="cvconf">conf '+esc(v.confidence)+(v.confidenceBand?' · '+esc(v.confidenceBand):'')+'</span>':'';
|
|
3329
|
-
var crit=v.critique?'<div class="cvcrit">'+esc(v.critique)+'</div>':'<div class="cvcrit dim">— no critique recorded for this criterion</div>';
|
|
3330
|
-
return '<div class="cvrow '+esc(res)+'"><div class="cvh"><span class="cvb '+esc(res)+'">'+esc(disp)+'</span><span class="cvid">'+esc(v.criterionId||'')+'</span>'+conf+'</div>'+crit+cvRefs(v.refs)+'</div>';}).join('');
|
|
3331
|
-
return '<div class="ctx cvblock"><div class="ctx-h">◇ how the judge reasoned — per-criterion verdict · critique-before-verdict · grounding refs ('+cvs.length+')</div><div class="cvbody">'+rows+'</div></div>';}
|
|
3332
|
-
function sideBySide(d){var ctx=d.context||{};
|
|
3333
|
-
// Gap A — the §2 "input + scenario" cell renders the RAW triggering INPUT (the
|
|
3334
|
-
// thing that fired the agent) ABOVE the judge's scenario LABEL. Long inputs
|
|
3335
|
-
// collapse into a <details> (lean, no JS) so the cell stays compact; short ones
|
|
3336
|
-
// render inline (clamped). ABSENT input ⇒ the cell shows the scenario alone (or
|
|
3337
|
-
// "—"). Font stays at the --fs-2xs (11px) floor + brand mono.
|
|
3338
|
-
var inputCell=function(raw,scen){
|
|
3339
|
-
var sc=scen?'<div class="iscn">scenario · '+esc(scen)+'</div>':'';
|
|
3340
|
-
if(!raw)return (scen?'<div class="ival">'+esc(scen)+'</div>':'—');
|
|
3341
|
-
var long=String(raw).length>180;
|
|
3342
|
-
var body=long
|
|
3343
|
-
? '<details class="iexp"><summary>raw input · '+String(raw).length+' chars</summary><pre class="iraw">'+esc(raw)+'</pre></details>'
|
|
3344
|
-
: '<pre class="iraw clamp">'+esc(raw)+'</pre>';
|
|
3345
|
-
return body+sc;};
|
|
3346
|
-
var refStr=function(rf){if(!rf)return '';if(typeof rf==='string')return rf;return [rf.obs,rf.path,rf.value].filter(Boolean).join(':');};
|
|
3347
|
-
// UI-4 — surface the judge's REASONING (why the verdict + why the exit-states were
|
|
3348
|
-
// concluded) from the EXISTING judge walk: an ordered why-chain band + the decide/bind
|
|
3349
|
-
// text inlined where it explains a conclusion. No emit-contract change — read-only over
|
|
3350
|
-
// the judge_steps already on the verdict file.
|
|
3351
|
-
var KORD={gather:0,context:1,examine:2,detect:3,bind:4,ground:5,critique:6,decide:7,verify:8};
|
|
3352
|
-
var allJs=(d.judgeSteps||[]).slice();
|
|
3353
|
-
var stepText=function(kind){return allJs.filter(function(s){return s.kind===kind;}).map(function(s){return s.text||'';}).filter(Boolean).join(' · ');};
|
|
3354
|
-
var decideWhy=stepText('decide')||stepText('critique');
|
|
3355
|
-
var stateWhy=[stepText('bind'),stepText('gather')].filter(Boolean).join(' · ');
|
|
3356
|
-
var chainSteps=allJs.slice().sort(function(a,b){var x=KORD[a.kind];var y=KORD[b.kind];return (x==null?9:x)-(y==null?9:y);});
|
|
3357
|
-
var whyChain=chainSteps.length?('<div class="ctx whychain"><div class="ctx-h">◇ judge reasoning — full why-chain (gather → bind → ground → decide)</div><div class="wc-body">'+chainSteps.map(function(s){var rs=refStr(s.ref);return '<div class="jstep"><span class="k '+esc(s.kind)+'">'+esc(s.kind)+'</span><span class="t">'+esc(s.text||'')+(rs?' <span class=ref>'+esc(rs)+'</span>':'')+'</span></div>';}).join('')+'</div></div>'):'';
|
|
3358
|
-
var verdictWhy=decideWhy?('<div class="band whyverdict"><div class="bh" style="color:var(--primary-soft)">◇ why this verdict</div><div class="wv-t">'+esc(decideWhy)+'</div></div>'):'';
|
|
3359
|
-
// -- §2 judge lane = per-step EVAL COVERAGE --
|
|
3360
|
-
// The judge lane (.step-r) used to filter judgeSteps by anchor === a.n -- but
|
|
3361
|
-
// judges never emit an anchor, so EVERY step rendered a bare dash. Instead we
|
|
3362
|
-
// map each agent step -> the per-criterion verdicts (d.criterionVerdicts) whose
|
|
3363
|
-
// grounding refs EXAMINED that step: a precise ref.obs === step.obs match plus
|
|
3364
|
-
// the tool-name fallback (ref.path === 'name' && ref.value === step.tool).
|
|
3365
|
-
// Each examining criterion renders one compact entry -- result + CODE/JUDGE tag
|
|
3366
|
-
// + criterionId + the judge reasoning (critique). A step no criterion references
|
|
3367
|
-
// says 'not examined by any eval' (honest), never a bare dash. Any judge step
|
|
3368
|
-
// that DOES carry a real anchor is still honored (future-proof).
|
|
3369
|
-
var cvAll=(d.criterionVerdicts||[]);
|
|
3370
|
-
var refExaminesStep=function(rf,a){
|
|
3371
|
-
if(!rf||typeof rf==='string')return false;
|
|
3372
|
-
if(a.obs&&rf.obs&&String(rf.obs)===String(a.obs))return true;
|
|
3373
|
-
if(rf.path==='name'&&rf.value!=null&&a.tool&&String(rf.value)===String(a.tool))return true;
|
|
3374
|
-
return false;};
|
|
3375
|
-
var covEntry=function(v,a){
|
|
3376
|
-
var res=v.result||'na';var disp=res==='uncertain'?'indeterminate':res;
|
|
3377
|
-
// the router carries TWO vocabularies: matrix-derived ('deterministic') and
|
|
3378
|
-
// mined ('code-based'); 'hybrid' is shared. A '[code-eval ...]'-prefixed critique
|
|
3379
|
-
// is the deterministic-logic fallback when the method is unset.
|
|
3380
|
-
var method=((typeof C!=='undefined'&&C[v.criterionId])||{}).m||'';
|
|
3381
|
-
var isCode=method==='deterministic'||method==='code-based'||method==='hybrid'||String(v.critique||'').trim().indexOf('[code-eval')===0;
|
|
3382
|
-
var tag=method==='hybrid'?'HYBRID':(isCode?'CODE':'JUDGE');
|
|
3383
|
-
var matched=(v.refs||[]).filter(function(rf){return typeof rf!=='string'&&refExaminesStep(rf,a);});
|
|
3384
|
-
var crit=v.critique?esc(v.critique):'<span class="dim">— no critique recorded</span>';
|
|
3385
|
-
return '<div class="jcov '+esc(res)+'"><div class="jcov-h"><span class="cvb '+esc(res)+'">'+esc(disp)+'</span><span class="jm '+(isCode?'code':'judge')+'" title="'+(isCode?'deterministically checked by a code-eval (not LLM-judged)':'reasoned by the LLM judge')+'">'+esc(tag)+'</span><span class="jcid">'+esc(v.criterionId||'')+'</span></div><div class="jcrit">'+crit+'</div>'+cvRefs(matched)+'</div>';};
|
|
3386
|
-
var rowsHtml='';(d.agentSteps||[]).forEach(function(a){
|
|
3387
|
-
// future-proof: a judge step that carries a REAL anchor still renders.
|
|
3388
|
-
var js=(d.judgeSteps||[]).filter(function(s){return s.anchor!=null&&String(s.anchor)===String(a.n)&&s.kind!=='context';});
|
|
3389
|
-
var anchoredHtml=js.map(function(s){var rs=refStr(s.ref);return '<div class="jstep"><span class="k '+esc(s.kind)+'">'+esc(s.kind)+'</span><span class="t">'+esc(s.text||'')+' '+(rs?'<span class=ref>'+esc(rs)+'</span>':'')+'</span></div>';}).join('');
|
|
3390
|
-
var examiners=cvAll.filter(function(v){return (v.refs||[]).some(function(rf){return refExaminesStep(rf,a);});});
|
|
3391
|
-
var covHtml=examiners.map(function(v){return covEntry(v,a);}).join('');
|
|
3392
|
-
var jhtml=anchoredHtml+covHtml;
|
|
3393
|
-
if(!jhtml)jhtml='<div class="jstep noexam"><span class="t">— not examined by any eval</span></div>';
|
|
3394
|
-
rowsHtml+='<div class="step-l"><div class="evb"><div class="top"><span class="tool">'+esc(a.tool||'')+'</span><span class="st '+esc(a.status||'')+'">'+esc(a.status||'')+'</span></div><div class="det">'+esc(a.detail||'')+'</div></div></div><div class="node '+esc(a.status||'')+'"><div class="ln"></div><div class="n">'+esc(a.n)+'</div><div class="ln"></div></div><div class="step-r"><div class="evb r">'+jhtml+'</div></div>';});
|
|
3395
|
-
var h=d.health||{};
|
|
3396
|
-
var sp=d.subjectProfile;
|
|
3397
|
-
var spHtml=sp?'<div class="ctx"><div class="ctx-h">◇ judge · subject profile (M1) · '+esc(sp.provenance||'')+'</div><div class="ctx-g"><div class="ctx-c"><div class="l">identity</div><div class="v">'+esc(sp.identity||'—')+'</div></div><div class="ctx-c"><div class="l">purpose</div><div class="v">'+esc(sp.purpose||'—')+'</div></div><div class="ctx-c"><div class="l">scope · harness</div><div class="v">'+esc((sp.scope||'—'))+' · harness: '+esc(sp.harness||'—')+'</div></div></div></div>':'';
|
|
3398
|
-
var u=d.understanding;
|
|
3399
|
-
var uHtml=u?'<div class="band"><div class="bh" style="color:var(--cyan)">◇ node-0 GATHER — understanding (M2)</div><div class="re-rephrase" style="margin-top:4px">“'+esc(u.rephrase||'')+'”</div></div>':'';
|
|
3400
|
-
var et=d.expectedTrajectory||[];
|
|
3401
|
-
var etHtml=et.length?'<div class="band"><div class="bh" style="color:var(--primary-soft)">◇ node-0.5 EXPECTED-TRAJECTORY (M3) — how it SHOULD have acted</div><ol class="re-ul" style="margin-top:4px">'+et.map(function(s,i){return '<li><b>'+esc(s.step||i+1)+'.</b> '+esc(s.expected||'')+(s.rationale?' <span class=re-rat>— '+esc(s.rationale)+'</span>':'')+'</li>';}).join('')+'</ol></div>':'';
|
|
3402
|
-
return '<div class="drillbox"><div style="display:flex;gap:9px;align-items:center;flex-wrap:wrap;margin-bottom:6px"><b class="mono">'+esc(d.traceId)+'</b><span class="chip">'+esc(d.route||'all')+'</span><span class="verd '+(d.verdict=='FAIL'?'fail':d.verdict=='PASS'?'pass':'inc')+'">'+esc(d.verdict)+'</span></div>'+
|
|
3403
|
-
resRouting(d.res||'judge-walk')+
|
|
3404
|
-
spHtml+
|
|
3405
|
-
verdictWhy+
|
|
3406
|
-
critiqueBlock(d.criterionVerdicts)+
|
|
3407
|
-
'<div class="ctx"><div class="ctx-h">◇ judge · gather context</div><div class="ctx-g"><div class="ctx-c"><div class="l">harness</div><div class="v">'+esc(ctx.harness||'—')+'</div></div><div class="ctx-c"><div class="l">input + scenario</div><div class="v">'+inputCell(d.input,ctx.scenario)+'</div></div><div class="ctx-c"><div class="l">exit states</div><div class="v">'+esc(ctx.exitStates||'—')+(stateWhy?'<div class="why-note"><span class="ref">why concluded:</span> '+esc(stateWhy)+'</div>':'')+'</div></div></div></div>'+
|
|
3408
|
-
whyChain+
|
|
3409
|
-
uHtml+etHtml+
|
|
3410
|
-
'<div class="lanehdr"><div class="a">target agent — what it did</div><div class="x">step</div><div class="j">judge — eval coverage (which criteria examined this step)</div></div>'+
|
|
3411
|
-
'<div class="grid2">'+rowsHtml+'<div class="band loc"><div class="bh">↯ localize (root, not symptom)</div><div style="font-size:var(--fs-sm);margin-top:4px">'+esc(d.localize||'—')+'</div></div></div>'+
|
|
3412
|
-
'<div class="health"><div class="hc"><div class="l">context</div><div class="v good">'+(h.contextGathered?'✓':'—')+'</div></div><div class="hc"><div class="l">grounded</div><div class="v good">'+esc(h.grounded||0)+'</div></div><div class="hc"><div class="l">assumed</div><div class="v '+((h.assumed||0)>0?'warn':'good')+'">'+esc(h.assumed||0)+'</div></div><div class="hc"><div class="l">root vs symptom</div><div class="v good">'+(h.stoppedAtSymptom?'symptom':'✓ root')+'</div></div></div></div>';}
|
|
3330
|
+
${SIDE_BY_SIDE_FRAGMENT_JS}
|
|
3413
3331
|
function scorecard(r){var order={fail:0,uncertain:1,indeterminate:1,pass:2,na:3};
|
|
3414
3332
|
var cells=Object.keys(r.c).sort(function(a,b){return (order[r.c[a]]||9)-(order[r.c[b]]||9);}).map(function(k){var v=r.c[k];var disp=v=='uncertain'?'indeterminate':v;
|
|
3415
3333
|
return '<div class="scc '+esc(v)+'"><span class="nm" title="'+esc((C[k]||{}).n||k)+'">'+esc(k)+'</span><span style="margin-left:auto;color:var(--muted)">'+esc(disp)+'</span></div>';}).join('');
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/report-fragments.ts — EV-3 shared client-JS render fragments.
|
|
3
|
+
* ---------------------------------------------------------------------------
|
|
4
|
+
* The step<->criterion side-by-side render functions, extracted VERBATIM from
|
|
5
|
+
* `render-eval-report.ts` so BOTH the eval report AND the `*review` UI
|
|
6
|
+
* (`build-review-ui.ts`) emit the identical browser-side renderer.
|
|
7
|
+
*
|
|
8
|
+
* `SIDE_BY_SIDE_FRAGMENT_JS` is the BYTE-IDENTICAL block (`cvRefs` +
|
|
9
|
+
* `critiqueBlock` + `sideBySide` — the latter carrying its local
|
|
10
|
+
* `refExaminesStep` + `covEntry`). render-eval-report splices it into its
|
|
11
|
+
* client script IN PLACE, so the report's emitted HTML is unchanged (guarded by
|
|
12
|
+
* the render-eval-report byte-identity tests + a before/after HTML diff).
|
|
13
|
+
*
|
|
14
|
+
* The fragment is provider-of-record for the render logic; its runtime deps
|
|
15
|
+
* (`esc`, `resRouting`, `resBadge`, and the globals `C` / `RES`) are
|
|
16
|
+
* supplied by whichever host script embeds it (render-eval-report defines them
|
|
17
|
+
* above the splice; the review UI defines compatible copies — see
|
|
18
|
+
* `REVIEW_SIDE_BY_SIDE_DEPS_JS`).
|
|
19
|
+
*
|
|
20
|
+
* `STEP_ROW_FRAGMENT_JS` is the review-UI VIRTUALIZATION mirror: standalone
|
|
21
|
+
* `refExaminesStepR` / `covEntryR` / `stepRowR` that emit ONE step triplet
|
|
22
|
+
* with markup IDENTICAL to `sideBySide`'s per-step output — so the review UI
|
|
23
|
+
* can window a 1,686-step card without materializing the whole DOM. A drift
|
|
24
|
+
* guard (`tests/report-fragments.test.ts`) evaluates both paths and asserts the
|
|
25
|
+
* per-step HTML matches, so the mirror can never silently diverge from the
|
|
26
|
+
* frozen fragment.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* BYTE-IDENTICAL extraction of the eval report's `cvRefs` + `critiqueBlock` +
|
|
31
|
+
* `sideBySide` client functions (`sideBySide` contains its local
|
|
32
|
+
* `refExaminesStep` + `covEntry`). Spliced verbatim by render-eval-report and
|
|
33
|
+
* reused by the review UI. DO NOT EDIT — regenerate from render-eval-report if
|
|
34
|
+
* that block ever changes, and re-verify byte-identity.
|
|
35
|
+
*/
|
|
36
|
+
export const SIDE_BY_SIDE_FRAGMENT_JS = `function cvRefs(refs){if(!refs||!refs.length)return '';
|
|
37
|
+
return '<div class="cvrefs">'+refs.map(function(rf){var s=(typeof rf==='string')?rf:[rf.obs,rf.path,rf.value].filter(Boolean).join(' · ');return s?'<span class="cvref">'+esc(s)+'</span>':'';}).join('')+'</div>';}
|
|
38
|
+
function critiqueBlock(cvs){if(!cvs||!cvs.length)return '';
|
|
39
|
+
var order={fail:0,uncertain:1,indeterminate:1,pass:2,na:3};
|
|
40
|
+
var rows=cvs.slice().sort(function(a,b){return (order[a.result]==null?9:order[a.result])-(order[b.result]==null?9:order[b.result]);}).map(function(v){
|
|
41
|
+
var res=v.result||'na';var disp=res==='uncertain'?'indeterminate':res;
|
|
42
|
+
var conf=(v.confidence!=null)?'<span class="cvconf">conf '+esc(v.confidence)+(v.confidenceBand?' · '+esc(v.confidenceBand):'')+'</span>':'';
|
|
43
|
+
var crit=v.critique?'<div class="cvcrit">'+esc(v.critique)+'</div>':'<div class="cvcrit dim">— no critique recorded for this criterion</div>';
|
|
44
|
+
return '<div class="cvrow '+esc(res)+'"><div class="cvh"><span class="cvb '+esc(res)+'">'+esc(disp)+'</span><span class="cvid">'+esc(v.criterionId||'')+'</span>'+conf+'</div>'+crit+cvRefs(v.refs)+'</div>';}).join('');
|
|
45
|
+
return '<div class="ctx cvblock"><div class="ctx-h">◇ how the judge reasoned — per-criterion verdict · critique-before-verdict · grounding refs ('+cvs.length+')</div><div class="cvbody">'+rows+'</div></div>';}
|
|
46
|
+
function sideBySide(d){var ctx=d.context||{};
|
|
47
|
+
// Gap A — the §2 "input + scenario" cell renders the RAW triggering INPUT (the
|
|
48
|
+
// thing that fired the agent) ABOVE the judge's scenario LABEL. Long inputs
|
|
49
|
+
// collapse into a <details> (lean, no JS) so the cell stays compact; short ones
|
|
50
|
+
// render inline (clamped). ABSENT input ⇒ the cell shows the scenario alone (or
|
|
51
|
+
// "—"). Font stays at the --fs-2xs (11px) floor + brand mono.
|
|
52
|
+
var inputCell=function(raw,scen){
|
|
53
|
+
var sc=scen?'<div class="iscn">scenario · '+esc(scen)+'</div>':'';
|
|
54
|
+
if(!raw)return (scen?'<div class="ival">'+esc(scen)+'</div>':'—');
|
|
55
|
+
var long=String(raw).length>180;
|
|
56
|
+
var body=long
|
|
57
|
+
? '<details class="iexp"><summary>raw input · '+String(raw).length+' chars</summary><pre class="iraw">'+esc(raw)+'</pre></details>'
|
|
58
|
+
: '<pre class="iraw clamp">'+esc(raw)+'</pre>';
|
|
59
|
+
return body+sc;};
|
|
60
|
+
var refStr=function(rf){if(!rf)return '';if(typeof rf==='string')return rf;return [rf.obs,rf.path,rf.value].filter(Boolean).join(':');};
|
|
61
|
+
// UI-4 — surface the judge's REASONING (why the verdict + why the exit-states were
|
|
62
|
+
// concluded) from the EXISTING judge walk: an ordered why-chain band + the decide/bind
|
|
63
|
+
// text inlined where it explains a conclusion. No emit-contract change — read-only over
|
|
64
|
+
// the judge_steps already on the verdict file.
|
|
65
|
+
var KORD={gather:0,context:1,examine:2,detect:3,bind:4,ground:5,critique:6,decide:7,verify:8};
|
|
66
|
+
var allJs=(d.judgeSteps||[]).slice();
|
|
67
|
+
var stepText=function(kind){return allJs.filter(function(s){return s.kind===kind;}).map(function(s){return s.text||'';}).filter(Boolean).join(' · ');};
|
|
68
|
+
var decideWhy=stepText('decide')||stepText('critique');
|
|
69
|
+
var stateWhy=[stepText('bind'),stepText('gather')].filter(Boolean).join(' · ');
|
|
70
|
+
var chainSteps=allJs.slice().sort(function(a,b){var x=KORD[a.kind];var y=KORD[b.kind];return (x==null?9:x)-(y==null?9:y);});
|
|
71
|
+
var whyChain=chainSteps.length?('<div class="ctx whychain"><div class="ctx-h">◇ judge reasoning — full why-chain (gather → bind → ground → decide)</div><div class="wc-body">'+chainSteps.map(function(s){var rs=refStr(s.ref);return '<div class="jstep"><span class="k '+esc(s.kind)+'">'+esc(s.kind)+'</span><span class="t">'+esc(s.text||'')+(rs?' <span class=ref>'+esc(rs)+'</span>':'')+'</span></div>';}).join('')+'</div></div>'):'';
|
|
72
|
+
var verdictWhy=decideWhy?('<div class="band whyverdict"><div class="bh" style="color:var(--primary-soft)">◇ why this verdict</div><div class="wv-t">'+esc(decideWhy)+'</div></div>'):'';
|
|
73
|
+
// -- §2 judge lane = per-step EVAL COVERAGE --
|
|
74
|
+
// The judge lane (.step-r) used to filter judgeSteps by anchor === a.n -- but
|
|
75
|
+
// judges never emit an anchor, so EVERY step rendered a bare dash. Instead we
|
|
76
|
+
// map each agent step -> the per-criterion verdicts (d.criterionVerdicts) whose
|
|
77
|
+
// grounding refs EXAMINED that step: a precise ref.obs === step.obs match plus
|
|
78
|
+
// the tool-name fallback (ref.path === 'name' && ref.value === step.tool).
|
|
79
|
+
// Each examining criterion renders one compact entry -- result + CODE/JUDGE tag
|
|
80
|
+
// + criterionId + the judge reasoning (critique). A step no criterion references
|
|
81
|
+
// says 'not examined by any eval' (honest), never a bare dash. Any judge step
|
|
82
|
+
// that DOES carry a real anchor is still honored (future-proof).
|
|
83
|
+
var cvAll=(d.criterionVerdicts||[]);
|
|
84
|
+
var refExaminesStep=function(rf,a){
|
|
85
|
+
if(!rf||typeof rf==='string')return false;
|
|
86
|
+
if(a.obs&&rf.obs&&String(rf.obs)===String(a.obs))return true;
|
|
87
|
+
if(rf.path==='name'&&rf.value!=null&&a.tool&&String(rf.value)===String(a.tool))return true;
|
|
88
|
+
return false;};
|
|
89
|
+
var covEntry=function(v,a){
|
|
90
|
+
var res=v.result||'na';var disp=res==='uncertain'?'indeterminate':res;
|
|
91
|
+
// the router carries TWO vocabularies: matrix-derived ('deterministic') and
|
|
92
|
+
// mined ('code-based'); 'hybrid' is shared. A '[code-eval ...]'-prefixed critique
|
|
93
|
+
// is the deterministic-logic fallback when the method is unset.
|
|
94
|
+
var method=((typeof C!=='undefined'&&C[v.criterionId])||{}).m||'';
|
|
95
|
+
var isCode=method==='deterministic'||method==='code-based'||method==='hybrid'||String(v.critique||'').trim().indexOf('[code-eval')===0;
|
|
96
|
+
var tag=method==='hybrid'?'HYBRID':(isCode?'CODE':'JUDGE');
|
|
97
|
+
var matched=(v.refs||[]).filter(function(rf){return typeof rf!=='string'&&refExaminesStep(rf,a);});
|
|
98
|
+
var crit=v.critique?esc(v.critique):'<span class="dim">— no critique recorded</span>';
|
|
99
|
+
return '<div class="jcov '+esc(res)+'"><div class="jcov-h"><span class="cvb '+esc(res)+'">'+esc(disp)+'</span><span class="jm '+(isCode?'code':'judge')+'" title="'+(isCode?'deterministically checked by a code-eval (not LLM-judged)':'reasoned by the LLM judge')+'">'+esc(tag)+'</span><span class="jcid">'+esc(v.criterionId||'')+'</span></div><div class="jcrit">'+crit+'</div>'+cvRefs(matched)+'</div>';};
|
|
100
|
+
var rowsHtml='';(d.agentSteps||[]).forEach(function(a){
|
|
101
|
+
// future-proof: a judge step that carries a REAL anchor still renders.
|
|
102
|
+
var js=(d.judgeSteps||[]).filter(function(s){return s.anchor!=null&&String(s.anchor)===String(a.n)&&s.kind!=='context';});
|
|
103
|
+
var anchoredHtml=js.map(function(s){var rs=refStr(s.ref);return '<div class="jstep"><span class="k '+esc(s.kind)+'">'+esc(s.kind)+'</span><span class="t">'+esc(s.text||'')+' '+(rs?'<span class=ref>'+esc(rs)+'</span>':'')+'</span></div>';}).join('');
|
|
104
|
+
var examiners=cvAll.filter(function(v){return (v.refs||[]).some(function(rf){return refExaminesStep(rf,a);});});
|
|
105
|
+
var covHtml=examiners.map(function(v){return covEntry(v,a);}).join('');
|
|
106
|
+
var jhtml=anchoredHtml+covHtml;
|
|
107
|
+
if(!jhtml)jhtml='<div class="jstep noexam"><span class="t">— not examined by any eval</span></div>';
|
|
108
|
+
rowsHtml+='<div class="step-l"><div class="evb"><div class="top"><span class="tool">'+esc(a.tool||'')+'</span><span class="st '+esc(a.status||'')+'">'+esc(a.status||'')+'</span></div><div class="det">'+esc(a.detail||'')+'</div></div></div><div class="node '+esc(a.status||'')+'"><div class="ln"></div><div class="n">'+esc(a.n)+'</div><div class="ln"></div></div><div class="step-r"><div class="evb r">'+jhtml+'</div></div>';});
|
|
109
|
+
var h=d.health||{};
|
|
110
|
+
var sp=d.subjectProfile;
|
|
111
|
+
var spHtml=sp?'<div class="ctx"><div class="ctx-h">◇ judge · subject profile (M1) · '+esc(sp.provenance||'')+'</div><div class="ctx-g"><div class="ctx-c"><div class="l">identity</div><div class="v">'+esc(sp.identity||'—')+'</div></div><div class="ctx-c"><div class="l">purpose</div><div class="v">'+esc(sp.purpose||'—')+'</div></div><div class="ctx-c"><div class="l">scope · harness</div><div class="v">'+esc((sp.scope||'—'))+' · harness: '+esc(sp.harness||'—')+'</div></div></div></div>':'';
|
|
112
|
+
var u=d.understanding;
|
|
113
|
+
var uHtml=u?'<div class="band"><div class="bh" style="color:var(--cyan)">◇ node-0 GATHER — understanding (M2)</div><div class="re-rephrase" style="margin-top:4px">“'+esc(u.rephrase||'')+'”</div></div>':'';
|
|
114
|
+
var et=d.expectedTrajectory||[];
|
|
115
|
+
var etHtml=et.length?'<div class="band"><div class="bh" style="color:var(--primary-soft)">◇ node-0.5 EXPECTED-TRAJECTORY (M3) — how it SHOULD have acted</div><ol class="re-ul" style="margin-top:4px">'+et.map(function(s,i){return '<li><b>'+esc(s.step||i+1)+'.</b> '+esc(s.expected||'')+(s.rationale?' <span class=re-rat>— '+esc(s.rationale)+'</span>':'')+'</li>';}).join('')+'</ol></div>':'';
|
|
116
|
+
return '<div class="drillbox"><div style="display:flex;gap:9px;align-items:center;flex-wrap:wrap;margin-bottom:6px"><b class="mono">'+esc(d.traceId)+'</b><span class="chip">'+esc(d.route||'all')+'</span><span class="verd '+(d.verdict=='FAIL'?'fail':d.verdict=='PASS'?'pass':'inc')+'">'+esc(d.verdict)+'</span></div>'+
|
|
117
|
+
resRouting(d.res||'judge-walk')+
|
|
118
|
+
spHtml+
|
|
119
|
+
verdictWhy+
|
|
120
|
+
critiqueBlock(d.criterionVerdicts)+
|
|
121
|
+
'<div class="ctx"><div class="ctx-h">◇ judge · gather context</div><div class="ctx-g"><div class="ctx-c"><div class="l">harness</div><div class="v">'+esc(ctx.harness||'—')+'</div></div><div class="ctx-c"><div class="l">input + scenario</div><div class="v">'+inputCell(d.input,ctx.scenario)+'</div></div><div class="ctx-c"><div class="l">exit states</div><div class="v">'+esc(ctx.exitStates||'—')+(stateWhy?'<div class="why-note"><span class="ref">why concluded:</span> '+esc(stateWhy)+'</div>':'')+'</div></div></div></div>'+
|
|
122
|
+
whyChain+
|
|
123
|
+
uHtml+etHtml+
|
|
124
|
+
'<div class="lanehdr"><div class="a">target agent — what it did</div><div class="x">step</div><div class="j">judge — eval coverage (which criteria examined this step)</div></div>'+
|
|
125
|
+
'<div class="grid2">'+rowsHtml+'<div class="band loc"><div class="bh">↯ localize (root, not symptom)</div><div style="font-size:var(--fs-sm);margin-top:4px">'+esc(d.localize||'—')+'</div></div></div>'+
|
|
126
|
+
'<div class="health"><div class="hc"><div class="l">context</div><div class="v good">'+(h.contextGathered?'✓':'—')+'</div></div><div class="hc"><div class="l">grounded</div><div class="v good">'+esc(h.grounded||0)+'</div></div><div class="hc"><div class="l">assumed</div><div class="v '+((h.assumed||0)>0?'warn':'good')+'">'+esc(h.assumed||0)+'</div></div><div class="hc"><div class="l">root vs symptom</div><div class="v good">'+(h.stoppedAtSymptom?'symptom':'✓ root')+'</div></div></div></div>';}`;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The runtime deps the extracted `sideBySide` needs — `esc` / `resBadge` /
|
|
130
|
+
* `resRouting` — copied BYTE-IDENTICALLY from render-eval-report's client script
|
|
131
|
+
* so the reused `sideBySide` behaves identically wherever it is embedded. The
|
|
132
|
+
* review UI embeds these (render-eval-report already defines them above its own
|
|
133
|
+
* splice). `RES` may be an empty map in the review UI — `resRouting`/`resBadge`
|
|
134
|
+
* degrade to '' when a resolution key is absent.
|
|
135
|
+
*/
|
|
136
|
+
export const REVIEW_SIDE_BY_SIDE_DEPS_JS = `function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
|
137
|
+
function resBadge(res){var m=RES[res];if(!m)return '';return '<span class="resbadge '+m.cls+'" title="'+esc(m.routing)+'">'+esc(m.badge)+'</span>';}
|
|
138
|
+
function resRouting(res){var m=RES[res];if(!m)return '';return '<div class="routing '+m.cls+'"><span class="routing-k">resolution</span><span class="routing-b '+m.cls+'">'+esc(m.badge)+'</span><span class="routing-v">'+esc(m.routing)+'</span></div>';}`;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The review-UI VIRTUALIZATION mirror. `stepRowR` emits ONE step triplet
|
|
142
|
+
* (`.step-l` + `.node` + `.step-r`) whose markup is CHARACTER-IDENTICAL to the
|
|
143
|
+
* per-step output of the frozen `sideBySide` (compare render-eval-report's
|
|
144
|
+
* `rowsHtml` loop). This lets the review UI window a 1,686-step card — rendering
|
|
145
|
+
* only the visible slice — instead of materializing the whole DOM (the EV-3
|
|
146
|
+
* wall). `refExaminesStepR` / `covEntryR` mirror `sideBySide`'s local
|
|
147
|
+
* `refExaminesStep` / `covEntry`. Depends on `esc` + `cvRefs` (from the deps +
|
|
148
|
+
* the frozen fragment). The drift guard in `tests/report-fragments.test.ts`
|
|
149
|
+
* evaluates both paths and asserts equality, so this mirror can never diverge.
|
|
150
|
+
*/
|
|
151
|
+
export const STEP_ROW_FRAGMENT_JS = `function refStrR(rf){if(!rf)return '';if(typeof rf==='string')return rf;return [rf.obs,rf.path,rf.value].filter(Boolean).join(':');}
|
|
152
|
+
function refExaminesStepR(rf,a){
|
|
153
|
+
if(!rf||typeof rf==='string')return false;
|
|
154
|
+
if(a.obs&&rf.obs&&String(rf.obs)===String(a.obs))return true;
|
|
155
|
+
if(rf.path==='name'&&rf.value!=null&&a.tool&&String(rf.value)===String(a.tool))return true;
|
|
156
|
+
return false;}
|
|
157
|
+
function covEntryR(v,a){
|
|
158
|
+
var res=v.result||'na';var disp=res==='uncertain'?'indeterminate':res;
|
|
159
|
+
var method=((typeof C!=='undefined'&&C&&C[v.criterionId])||{}).m||'';
|
|
160
|
+
var isCode=method==='deterministic'||method==='code-based'||method==='hybrid'||String(v.critique||'').trim().indexOf('[code-eval')===0;
|
|
161
|
+
var tag=method==='hybrid'?'HYBRID':(isCode?'CODE':'JUDGE');
|
|
162
|
+
var matched=(v.refs||[]).filter(function(rf){return typeof rf!=='string'&&refExaminesStepR(rf,a);});
|
|
163
|
+
var crit=v.critique?esc(v.critique):'<span class="dim">— no critique recorded</span>';
|
|
164
|
+
return '<div class="jcov '+esc(res)+'"><div class="jcov-h"><span class="cvb '+esc(res)+'">'+esc(disp)+'</span><span class="jm '+(isCode?'code':'judge')+'" title="'+(isCode?'deterministically checked by a code-eval (not LLM-judged)':'reasoned by the LLM judge')+'">'+esc(tag)+'</span><span class="jcid">'+esc(v.criterionId||'')+'</span></div><div class="jcrit">'+crit+'</div>'+cvRefs(matched)+'</div>';}
|
|
165
|
+
function stepRowInnerR(a,cvAll,judgeSteps){
|
|
166
|
+
var js=(judgeSteps||[]).filter(function(s){return s.anchor!=null&&String(s.anchor)===String(a.n)&&s.kind!=='context';});
|
|
167
|
+
var anchoredHtml=js.map(function(s){var rs=refStrR(s.ref);return '<div class="jstep"><span class="k '+esc(s.kind)+'">'+esc(s.kind)+'</span><span class="t">'+esc(s.text||'')+' '+(rs?'<span class=ref>'+esc(rs)+'</span>':'')+'</span></div>';}).join('');
|
|
168
|
+
var examiners=(cvAll||[]).filter(function(v){return (v.refs||[]).some(function(rf){return refExaminesStepR(rf,a);});});
|
|
169
|
+
var covHtml=examiners.map(function(v){return covEntryR(v,a);}).join('');
|
|
170
|
+
var jhtml=anchoredHtml+covHtml;
|
|
171
|
+
if(!jhtml)jhtml='<div class="jstep noexam"><span class="t">— not examined by any eval</span></div>';
|
|
172
|
+
return '<div class="step-l"><div class="evb"><div class="top"><span class="tool">'+esc(a.tool||'')+'</span><span class="st '+esc(a.status||'')+'">'+esc(a.status||'')+'</span></div><div class="det">'+esc(a.detail||'')+'</div></div></div><div class="node '+esc(a.status||'')+'"><div class="ln"></div><div class="n">'+esc(a.n)+'</div><div class="ln"></div></div><div class="step-r"><div class="evb r">'+jhtml+'</div></div>';}
|
|
173
|
+
function stepExaminedByR(a,cvAll){return (cvAll||[]).some(function(v){return (v.refs||[]).some(function(rf){return refExaminesStepR(rf,a);});});}`;
|
|
@@ -35,12 +35,18 @@ export const AdlStage = {
|
|
|
35
35
|
Build: "build",
|
|
36
36
|
Evaluate: "evaluate",
|
|
37
37
|
Diagnose: "diagnose",
|
|
38
|
-
|
|
38
|
+
Optimize: "optimize",
|
|
39
39
|
Audit: "audit",
|
|
40
40
|
} as const;
|
|
41
41
|
export type AdlStageValue = (typeof AdlStage)[keyof typeof AdlStage];
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
// Mirrors the orchestrator handover-contract SubjectKind. The CANONICAL inter-stage
|
|
44
|
+
// subject vocabulary is `agent | skill | code` (matches config-schema TargetSubject).
|
|
45
|
+
// `code` (Wave-2 follow-up) is ADDITIVE — every existing skill/agent handoff still
|
|
46
|
+
// validates; it only lets a `code` subject route through *evaluate's failure handoff
|
|
47
|
+
// the SAME way it flows through the orchestrator handover-contract (the two are kept
|
|
48
|
+
// in lock-step by design; the evaluator re-implements the shape, never imports it).
|
|
49
|
+
export const SubjectKind = { Skill: "skill", Agent: "agent", Code: "code" } as const;
|
|
44
50
|
export type SubjectKindValue = (typeof SubjectKind)[keyof typeof SubjectKind];
|
|
45
51
|
|
|
46
52
|
export const ArtifactKind = {
|
|
@@ -67,7 +73,11 @@ export type EscalationPolicyValue =
|
|
|
67
73
|
// ── TypeBox schemas (closed objects — auditable boundary) ───────────────────
|
|
68
74
|
const SubjectSchema = Type.Object(
|
|
69
75
|
{
|
|
70
|
-
kind: Type.Union([
|
|
76
|
+
kind: Type.Union([
|
|
77
|
+
Type.Literal(SubjectKind.Skill),
|
|
78
|
+
Type.Literal(SubjectKind.Agent),
|
|
79
|
+
Type.Literal(SubjectKind.Code),
|
|
80
|
+
]),
|
|
71
81
|
name: Type.String({ minLength: 1 }),
|
|
72
82
|
path: Type.String({ minLength: 1 }),
|
|
73
83
|
},
|
|
@@ -132,7 +142,7 @@ const HandoverBundleSchema = Type.Object(
|
|
|
132
142
|
Type.Literal(AdlStage.Build),
|
|
133
143
|
Type.Literal(AdlStage.Evaluate),
|
|
134
144
|
Type.Literal(AdlStage.Diagnose),
|
|
135
|
-
Type.Literal(AdlStage.
|
|
145
|
+
Type.Literal(AdlStage.Optimize),
|
|
136
146
|
Type.Literal(AdlStage.Audit),
|
|
137
147
|
]),
|
|
138
148
|
subject: SubjectSchema,
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
type EvalMatrix,
|
|
33
33
|
type RowResultValue,
|
|
34
34
|
type ScorecardCriterion,
|
|
35
|
+
CheckMethod,
|
|
35
36
|
RowResult,
|
|
36
37
|
Track,
|
|
37
38
|
trackForCheckMethod,
|
|
@@ -84,14 +85,96 @@ function isNonEmpty(value: unknown): boolean {
|
|
|
84
85
|
return true;
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
/** A STRUCTURED, non-empty value: a non-empty object or array. A present-but-
|
|
89
|
+
* scalar/empty artifact is NOT structured — the typebox-schema re-exec fails it
|
|
90
|
+
* (a hollow presence-pass under the old proxy). PURE. */
|
|
91
|
+
function isStructuredNonEmpty(value: unknown): boolean {
|
|
92
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
93
|
+
if (value !== null && typeof value === "object") return Object.keys(value).length > 0;
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Scan a produced artifact for an EXPLICIT gate-failure marker: a falsy pass
|
|
98
|
+
* flag (`pass`/`ok`/`passed` === false) or a failing `status` stamp
|
|
99
|
+
* (`fail`/`failed`/`error`). Conservative + shallow so it stays deterministic
|
|
100
|
+
* and does not false-fail on unrelated substrings. PURE. */
|
|
101
|
+
function hasFailureMarker(value: unknown): boolean {
|
|
102
|
+
if (value === null || typeof value !== "object") return false;
|
|
103
|
+
const obj = value as Record<string, unknown>;
|
|
104
|
+
for (const flag of ["pass", "ok", "passed"] as const) {
|
|
105
|
+
if (obj[flag] === false) return true;
|
|
106
|
+
}
|
|
107
|
+
const status = obj["status"];
|
|
108
|
+
if (typeof status === "string") {
|
|
109
|
+
const s = status.toLowerCase();
|
|
110
|
+
if (s === "fail" || s === "failed" || s === "error") return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Item #4 — the REAL live re-execution of a deterministic row. Replaces the
|
|
117
|
+
* presence-proxy (`the artifact exists → mark it covered`) with an ACTUAL
|
|
118
|
+
* checkMethod-keyed predicate over the artifact's LIVE content:
|
|
119
|
+
* - typebox-schema : the produced artifact must be a STRUCTURED, non-empty
|
|
120
|
+
* object/array — a present-but-malformed scalar now FAILS
|
|
121
|
+
* instead of hollow-passing (real shape conformance).
|
|
122
|
+
* - gate : the evidence must be present AND carry NO explicit
|
|
123
|
+
* failure marker (fail-loud gate — `hasFailureMarker`).
|
|
124
|
+
* - deterministic-script: the produced artifact must be present + non-empty
|
|
125
|
+
* (the script actually emitted its output).
|
|
126
|
+
* Returns pass/fail when the criterion maps to a live bundle artifact; returns
|
|
127
|
+
* `null` when NO artifact answers it offline — the caller then records the
|
|
128
|
+
* documented seam-skip. A genuinely un-runnable row would require executing the
|
|
129
|
+
* SUBJECT'S OWN script, which the judge-only cell never does (EV-051) and which
|
|
130
|
+
* dispatches NO sub-agents (PR-ORCH-01). PURE + deterministic (no clock/random/
|
|
131
|
+
* network) ⇒ C-PIN byte-identical reruns.
|
|
132
|
+
*/
|
|
133
|
+
export function reexecDeterministicCheck(
|
|
134
|
+
criterion: Criterion,
|
|
135
|
+
bundle: RunBundle,
|
|
136
|
+
): RowResultValue | null {
|
|
137
|
+
const artifactKey = evidenceArtifactFor(criterion, bundle);
|
|
138
|
+
if (artifactKey == null) return null; // no live artifact → not re-executable here
|
|
139
|
+
const value = bundle.data[artifactKey];
|
|
140
|
+
// HONESTY on the depth of each re-exec (do NOT mistake these for deep checks):
|
|
141
|
+
// - typebox-schema : a real SHAPE upgrade over presence (rejects a present-but-
|
|
142
|
+
// scalar), but it is NOT a full TypeBox `Value.Check` against
|
|
143
|
+
// the produced artifact's declared schema — it asserts
|
|
144
|
+
// "structured + non-empty", not field-level conformance.
|
|
145
|
+
// - gate : `hasFailureMarker` is TOP-LEVEL-ONLY (scans the object's own
|
|
146
|
+
// pass/ok/passed/status keys) — it does NOT recurse into nested
|
|
147
|
+
// gate evidence, so a failure buried in a child object is missed.
|
|
148
|
+
// - deterministic-script : semantically ≡ the OLD presence proxy (present +
|
|
149
|
+
// non-empty). It is re-run against LIVE data (so it is a real
|
|
150
|
+
// re-execution, not a cached verdict), but carries no deeper
|
|
151
|
+
// predicate than presence. Deepening any of these to a true
|
|
152
|
+
// re-run of the subject's own script is the documented seam
|
|
153
|
+
// (out of scope for a judge-only / no-sub-agent cell).
|
|
154
|
+
switch (criterion.checkMethod) {
|
|
155
|
+
case CheckMethod.TypeboxSchema:
|
|
156
|
+
return isStructuredNonEmpty(value) ? RowResult.Pass : RowResult.Fail;
|
|
157
|
+
case CheckMethod.Gate:
|
|
158
|
+
return isNonEmpty(value) && !hasFailureMarker(value) ? RowResult.Pass : RowResult.Fail;
|
|
159
|
+
case CheckMethod.DeterministicScript:
|
|
160
|
+
default:
|
|
161
|
+
return isNonEmpty(value) ? RowResult.Pass : RowResult.Fail;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
87
165
|
export interface EvaluateOptions {
|
|
88
166
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
167
|
+
* Item #4 — the LIVE RE-EXEC executor. When supplied it RUNS the real
|
|
168
|
+
* deterministic check against live bundle data (see `reexecDeterministicCheck`,
|
|
169
|
+
* the default) and its pass/fail return is authoritative — replacing the
|
|
170
|
+
* presence-proxy that marked a row covered from mere artifact existence. A
|
|
171
|
+
* `null` return means "this executor cannot answer this row" ⇒ the legacy
|
|
172
|
+
* (byte-stable) path runs. ABSENT ⇒ no re-exec wired: fully back-compatible,
|
|
173
|
+
* byte-identical to before (the presence proxy + documented seam-skip). The
|
|
174
|
+
* executor is PURE + dispatches NO sub-agents (PR-ORCH-01) + never fixes the
|
|
175
|
+
* subject (EV-051).
|
|
93
176
|
*/
|
|
94
|
-
liveReexec?: (criterion: Criterion) => RowResultValue | null;
|
|
177
|
+
liveReexec?: (criterion: Criterion, bundle: RunBundle) => RowResultValue | null;
|
|
95
178
|
}
|
|
96
179
|
|
|
97
180
|
/**
|
|
@@ -104,21 +187,31 @@ export function evaluateDeterministic(
|
|
|
104
187
|
opts: EvaluateOptions = {},
|
|
105
188
|
): ScorecardCriterion {
|
|
106
189
|
const track = Track.Deterministic;
|
|
190
|
+
|
|
191
|
+
// Item #4 — LIVE RE-EXEC FIRST. When an executor is wired it RUNS the real
|
|
192
|
+
// deterministic check against live data; a pass/fail is authoritative and
|
|
193
|
+
// REPLACES the presence-proxy (no more "an artifact exists → covered"). A null
|
|
194
|
+
// return means the executor could not answer this row ⇒ fall through to the
|
|
195
|
+
// (byte-stable) legacy path below.
|
|
196
|
+
const live = opts.liveReexec?.(criterion, bundle) ?? null;
|
|
197
|
+
if (live === RowResult.Pass || live === RowResult.Fail || live === RowResult.Incomplete) {
|
|
198
|
+
return {
|
|
199
|
+
dimension: criterion.dimension,
|
|
200
|
+
severity: criterion.severity,
|
|
201
|
+
checkMethod: criterion.checkMethod,
|
|
202
|
+
track,
|
|
203
|
+
result: live,
|
|
204
|
+
detail: `live re-exec (${criterion.checkMethod}): real deterministic check over the produced artifact → ${live}`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
107
208
|
const artifactKey = evidenceArtifactFor(criterion, bundle);
|
|
108
209
|
|
|
109
210
|
if (artifactKey == null) {
|
|
110
|
-
// No bundle artifact answers this row offline ->
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
dimension: criterion.dimension,
|
|
115
|
-
severity: criterion.severity,
|
|
116
|
-
checkMethod: criterion.checkMethod,
|
|
117
|
-
track,
|
|
118
|
-
result: live,
|
|
119
|
-
detail: "evaluated via live-reexec callback",
|
|
120
|
-
};
|
|
121
|
-
}
|
|
211
|
+
// No bundle artifact answers this row offline -> documented seam-skip. NOT
|
|
212
|
+
// silent: the coverage-honesty warning (computeCoverage) surfaces it, and
|
|
213
|
+
// fully executing it would require running the subject's OWN script — which
|
|
214
|
+
// the judge-only cell never does (EV-051) and which dispatches no sub-agents.
|
|
122
215
|
return {
|
|
123
216
|
dimension: criterion.dimension,
|
|
124
217
|
severity: criterion.severity,
|
|
@@ -130,6 +223,9 @@ export function evaluateDeterministic(
|
|
|
130
223
|
};
|
|
131
224
|
}
|
|
132
225
|
|
|
226
|
+
// LEGACY presence proxy — reached ONLY when no live-reexec executor is wired
|
|
227
|
+
// (kept for byte-identity with pre-Item-#4 callers). With the default executor
|
|
228
|
+
// wired (audit-run.ts) this branch is never taken for a mapped row.
|
|
133
229
|
const present = isNonEmpty(bundle.data[artifactKey]);
|
|
134
230
|
return {
|
|
135
231
|
dimension: criterion.dimension,
|
|
@@ -138,7 +234,7 @@ export function evaluateDeterministic(
|
|
|
138
234
|
track,
|
|
139
235
|
result: present ? RowResult.Pass : RowResult.Fail,
|
|
140
236
|
detail: present
|
|
141
|
-
? `evidence artifact '${artifactKey}' present + non-empty`
|
|
237
|
+
? `evidence artifact '${artifactKey}' present + non-empty (presence proxy — no live re-exec wired)`
|
|
142
238
|
: `evidence artifact '${artifactKey}' MISSING/empty in bundle (fail-loud)`,
|
|
143
239
|
};
|
|
144
240
|
}
|