@fede0089/skill-eval 1.2.2 → 1.3.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.
@@ -87,38 +87,62 @@ function renderMetricsGrid(report) {
87
87
  </div>`;
88
88
  }
89
89
  // ---------------------------------------------------------------------------
90
- // Trial details
90
+ // Expectations × Variants table (per-task drill-down)
91
91
  // ---------------------------------------------------------------------------
92
- function renderAssertions(assertions) {
93
- if (assertions.length === 0)
94
- return '<p class="muted">No assertions recorded.</p>';
95
- return assertions.map(a => {
96
- const icon = a.passed ? '✓' : '✗';
97
- const cls = a.passed ? 'assert-pass' : 'assert-fail';
98
- return `<div class="assertion ${cls}">
99
- <span class="assert-icon">${icon}</span>
100
- <div class="assert-body">
101
- <div class="assert-text">${escapeHtml(a.assertion)}</div>
102
- ${a.reason ? `<div class="assert-reason">${escapeHtml(a.reason)}</div>` : ''}
103
- </div>
104
- </div>`;
92
+ function renderExpectationCell(trials, expIdx, variant, taskId, clickable, totalCols) {
93
+ const passed = trials.filter(t => t.assertionResults[expIdx]?.passed).length;
94
+ const total = trials.length;
95
+ const rate = total > 0 ? passed / total : 0;
96
+ const colorCls = passColorClass(rate);
97
+ const ratePct = formatPercent(rate);
98
+ const frac = `${passed} / ${total}`;
99
+ const detailId = `exp-detail-${taskId}-${expIdx}-${variant}`;
100
+ const dataAttr = clickable ? ` data-detail="${escapeHtml(detailId)}"` : '';
101
+ const cell = `<td class="pass-cell ${colorCls}"${dataAttr}><span class="rate">${ratePct}</span><span class="frac">${frac}</span></td>`;
102
+ if (!clickable)
103
+ return { cell, detail: '' };
104
+ const lines = trials.map(t => {
105
+ const r = t.assertionResults[expIdx];
106
+ const ok = !!r?.passed;
107
+ const reason = r?.reason ?? '';
108
+ const icon = ok ? '✓' : '✗';
109
+ const iconCls = ok ? 'pass' : 'fail';
110
+ return `<div class="exp-trial-line"><div class="exp-trial-icon ${iconCls}">${icon}</div><div class="exp-trial-body"><span class="trial-label">Trial ${t.id}</span><span class="trial-reason">${escapeHtml(reason)}</span></div></div>`;
105
111
  }).join('');
112
+ const detail = `<tr class="exp-detail-row" id="${escapeHtml(detailId)}"><td colspan="${totalCols}"><div class="exp-detail-inner"><div class="exp-detail-header">Judge per trial · <span class="variant-pill">${escapeHtml(variant)}</span></div>${lines}</div></td></tr>`;
113
+ return { cell, detail };
106
114
  }
107
- function renderTrial(trial) {
108
- const passedCount = trial.assertionResults.filter(r => r.passed).length;
109
- const totalCount = trial.assertionResults.length;
110
- const isPartial = !trial.trialPassed && !trial.isError && passedCount > 0;
111
- const cls = trial.isError ? 'trial-error' : trial.trialPassed ? 'trial-pass' : isPartial ? 'trial-partial' : 'trial-fail';
112
- const badge = trial.isError
113
- ? '<span class="pill amber">! ERROR</span>'
114
- : trial.trialPassed
115
- ? '<span class="pill green">✓ PASS</span>'
116
- : isPartial
117
- ? `<span class="pill amber">~ PARTIAL ${passedCount}/${totalCount}</span>`
118
- : '<span class="pill red">✗ NOT PASSED</span>';
119
- return `<div class="trial ${cls}">
120
- <div class="trial-header">Trial ${trial.id} ${badge}</div>
121
- <div class="trial-assertions">${renderAssertions(trial.assertionResults)}</div>
115
+ function renderExpectationsTable(result, allVersions, isFunctionalEval) {
116
+ let canonical = [];
117
+ for (const v of allVersions) {
118
+ const trials = v === 'baseline' ? result.baselineTrials : result.skillTrials[v];
119
+ if (trials && trials.length > 0 && trials[0].assertionResults.length > 0) {
120
+ canonical = trials[0].assertionResults;
121
+ break;
122
+ }
123
+ }
124
+ if (canonical.length === 0) {
125
+ return '<p class="muted">No assertions recorded.</p>';
126
+ }
127
+ const totalCols = 1 + allVersions.length;
128
+ const headerCells = allVersions.map(v => `<th class="variant-col">${escapeHtml(v)}</th>`).join('');
129
+ const rows = canonical.map((a, expIdx) => {
130
+ const cells = [];
131
+ const details = [];
132
+ for (const v of allVersions) {
133
+ const trials = v === 'baseline' ? result.baselineTrials : result.skillTrials[v];
134
+ const { cell, detail } = renderExpectationCell(trials, expIdx, v, result.taskId, isFunctionalEval, totalCols);
135
+ cells.push(cell);
136
+ if (detail)
137
+ details.push(detail);
138
+ }
139
+ return `<tr class="exp-row"><td class="exp-text">${escapeHtml(a.assertion)}</td>${cells.join('')}</tr>${details.join('')}`;
140
+ }).join('');
141
+ return `<div class="expectations-table-wrap">
142
+ <table class="expectations-table">
143
+ <thead><tr><th class="exp-col">Expectation</th>${headerCells}</tr></thead>
144
+ <tbody>${rows}</tbody>
145
+ </table>
122
146
  </div>`;
123
147
  }
124
148
  function avgTrialTokens(trials) {
@@ -175,16 +199,11 @@ function renderTaskMiniGrid(result, isFunctionalEval) {
175
199
  </div>`;
176
200
  }
177
201
  function renderTaskDetails(result, isFunctionalEval) {
202
+ const skillVersions = Object.keys(result.skillTrials);
203
+ const allVersions = isFunctionalEval ? ['baseline', ...skillVersions] : skillVersions;
178
204
  const sections = [];
179
205
  sections.push(renderTaskMiniGrid(result, isFunctionalEval));
180
- if (isFunctionalEval && result.baselineTrials && result.baselineTrials.length > 0) {
181
- sections.push('<div class="trial-group-label">Baseline</div>');
182
- sections.push(...result.baselineTrials.map(t => renderTrial(t)));
183
- }
184
- for (const version of Object.keys(result.skillTrials)) {
185
- sections.push(`<div class="trial-group-label">${version}</div>`);
186
- sections.push(...result.skillTrials[version].map(t => renderTrial(t)));
187
- }
206
+ sections.push(renderExpectationsTable(result, allVersions, isFunctionalEval));
188
207
  return `<div class="task-details" id="details-${result.taskId}">${sections.join('')}</div>`;
189
208
  }
190
209
  // ---------------------------------------------------------------------------
@@ -281,31 +300,42 @@ tr:last-child td { border-bottom: none; }
281
300
  .details-row > td { padding: 0; background: #f8fafc; }
282
301
  .task-details { display: none; padding: 12px 16px; }
283
302
  .task-details.visible { display: block; }
284
- .trial-group-label { font-size: 11px; font-weight: 600; text-transform: uppercase; color: #94a3b8; letter-spacing: 0.05em; margin: 8px 0 4px; }
285
303
 
286
- /* Trials */
287
- .trial { border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 8px; overflow: hidden; }
288
- .trial-header { display: flex; align-items: center; gap: 8px; padding: 8px 12px; font-weight: 500; font-size: 13px; background: #f8fafc; }
289
- .trial-pass .trial-header { border-left: 3px solid #22c55e; }
290
- .trial-partial .trial-header { border-left: 3px solid #f59e0b; }
291
- .trial-fail .trial-header { border-left: 3px solid #ef4444; }
292
- .trial-error .trial-header { border-left: 3px solid #f59e0b; }
293
- .trial-assertions { padding: 8px 12px; display: flex; flex-direction: column; gap: 6px; }
304
+ /* Expectations × Variants table */
305
+ .expectations-table-wrap { border: 1px solid #e2e8f0; border-radius: 6px; overflow: hidden; background: #fff; }
306
+ .expectations-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
307
+ .expectations-table thead th { background: #f1f5f9; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #475569; padding: 10px 14px; text-align: left; border-bottom: 1px solid #e2e8f0; }
308
+ .expectations-table thead th.exp-col { width: auto; }
309
+ .expectations-table thead th.variant-col { width: 110px; text-align: center; }
310
+ .expectations-table tbody td { padding: 10px 14px; border-bottom: 1px solid #f1f5f9; vertical-align: middle; font-size: 13px; }
311
+ .expectations-table tbody tr.exp-row:last-of-type > td { border-bottom: none; }
312
+ .exp-text { color: #1e293b; line-height: 1.4; word-break: break-word; }
294
313
 
295
- /* Pills */
296
- .pill { display: inline-block; font-size: 10px; font-weight: 700; padding: 2px 7px; border-radius: 99px; letter-spacing: 0.05em; }
297
- .pill.green { background: #dcfce7; color: #15803d; }
298
- .pill.red { background: #fee2e2; color: #b91c1c; }
299
- .pill.amber { background: #fef3c7; color: #92400e; }
314
+ .pass-cell { text-align: center; border-left: 1px solid #f1f5f9; }
315
+ .pass-cell[data-detail] { cursor: pointer; user-select: none; transition: background 0.12s; }
316
+ .pass-cell[data-detail]:hover { background: #f1f5f9; }
317
+ .pass-cell.open { background: #e0f2fe; }
318
+ .pass-cell .rate { display: block; font-size: 13px; font-weight: 700; line-height: 1.1; }
319
+ .pass-cell .frac { display: block; font-size: 10px; font-weight: 600; color: #94a3b8; margin-top: 2px; letter-spacing: 0.03em; }
320
+ .pass-cell.green .rate { color: #16a34a; }
321
+ .pass-cell.amber .rate { color: #d97706; }
322
+ .pass-cell.red .rate { color: #dc2626; }
323
+
324
+ .exp-detail-row { display: none; }
325
+ .exp-detail-row.visible { display: table-row; }
326
+ .exp-detail-row > td { padding: 0; background: #f8fafc; border-bottom: 1px solid #f1f5f9; }
327
+ .exp-detail-inner { padding: 12px 14px 14px; border-left: 3px solid #3b82f6; }
328
+ .exp-detail-header { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #64748b; margin-bottom: 8px; }
329
+ .exp-detail-header .variant-pill { display: inline-block; background: #e0f2fe; color: #0369a1; padding: 1px 7px; border-radius: 4px; margin-left: 4px; font-weight: 700; }
330
+ .exp-trial-line { display: flex; gap: 8px; padding: 6px 0; border-top: 1px dashed #e2e8f0; }
331
+ .exp-trial-line:first-of-type { border-top: none; }
332
+ .exp-trial-icon { flex-shrink: 0; font-size: 13px; font-weight: 700; line-height: 1.4; width: 16px; }
333
+ .exp-trial-icon.pass { color: #16a34a; }
334
+ .exp-trial-icon.fail { color: #dc2626; }
335
+ .exp-trial-body { flex: 1; min-width: 0; font-size: 12px; color: #334155; line-height: 1.45; }
336
+ .exp-trial-body .trial-label { font-weight: 700; color: #475569; margin-right: 4px; }
337
+ .exp-trial-body .trial-reason { color: #64748b; }
300
338
 
301
- /* Assertions */
302
- .assertion { display: flex; gap: 8px; }
303
- .assert-icon { flex-shrink: 0; font-size: 14px; margin-top: 1px; }
304
- .assert-pass .assert-icon { color: #16a34a; }
305
- .assert-fail .assert-icon { color: #dc2626; }
306
- .assert-body { flex: 1; min-width: 0; }
307
- .assert-text { font-size: 13px; color: #1e293b; word-break: break-word; }
308
- .assert-reason { font-size: 12px; color: #64748b; margin-top: 2px; word-break: break-word; }
309
339
  .muted { color: #94a3b8; font-size: 13px; }
310
340
  </style>
311
341
  </head>
@@ -345,6 +375,16 @@ tr:last-child td { border-bottom: none; }
345
375
  btn.textContent = isOpen ? '▶' : '▼';
346
376
  });
347
377
  });
378
+
379
+ document.querySelectorAll('.pass-cell[data-detail]').forEach(function (cell) {
380
+ cell.addEventListener('click', function () {
381
+ const detailRow = document.getElementById(cell.getAttribute('data-detail'));
382
+ if (!detailRow) return;
383
+ const isOpen = detailRow.classList.contains('visible');
384
+ detailRow.classList.toggle('visible', !isOpen);
385
+ cell.classList.toggle('open', !isOpen);
386
+ });
387
+ });
348
388
  }());
349
389
  </script>
350
390
  </body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fede0089/skill-eval",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "CLI to evaluate agent skills triggering",
5
5
  "main": "dist/index.js",
6
6
  "bin": {