@fede0089/skill-eval 1.0.0 → 1.1.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/index.js CHANGED
@@ -40,6 +40,7 @@ program
40
40
  .option('--trials <number>', 'Number of trials per task for pass@k calculation')
41
41
  .option('--timeout <seconds>', 'Agent timeout in seconds')
42
42
  .option('--eval-id <id>', 'Run only the eval with this ID (numeric)')
43
+ .option('--compare-ref [refs...]', 'Compare against historical git references')
43
44
  .action((agent, options) => {
44
45
  const workspace = path.resolve(options.workspace);
45
46
  const selectedAgent = agent || DEFAULT_AGENT;
@@ -47,7 +48,8 @@ program
47
48
  const numTrials = options.trials !== undefined ? (parseInt(options.trials, 10) || 3) : 3;
48
49
  const timeoutMs = options.timeout ? parseInt(options.timeout, 10) * 1000 : undefined;
49
50
  const evalId = options.evalId !== undefined ? parseInt(options.evalId, 10) : undefined;
50
- triggerCommand(selectedAgent, workspace, options.skill, maxAgents, undefined, numTrials, new HtmlReporter(), timeoutMs, evalId).catch(errorHandler);
51
+ const compareRefs = options.compareRef || [];
52
+ triggerCommand(selectedAgent, workspace, options.skill, maxAgents, undefined, numTrials, new HtmlReporter(), timeoutMs, evalId, compareRefs).catch(errorHandler);
51
53
  });
52
54
  program
53
55
  .command('functional [agent]')
@@ -58,6 +60,7 @@ program
58
60
  .option('--trials <number>', 'Number of trials per task for pass@k calculation')
59
61
  .option('--timeout <seconds>', 'Agent timeout in seconds')
60
62
  .option('--eval-id <id>', 'Run only the eval with this ID (numeric)')
63
+ .option('--compare-ref [refs...]', 'Compare against historical git references')
61
64
  .action((agent, options) => {
62
65
  const workspace = path.resolve(options.workspace);
63
66
  const selectedAgent = agent || DEFAULT_AGENT;
@@ -65,7 +68,8 @@ program
65
68
  const numTrials = options.trials !== undefined ? (parseInt(options.trials, 10) || 3) : 3;
66
69
  const timeoutMs = options.timeout ? parseInt(options.timeout, 10) * 1000 : undefined;
67
70
  const evalId = options.evalId !== undefined ? parseInt(options.evalId, 10) : undefined;
68
- functionalCommand(selectedAgent, workspace, options.skill, maxAgents, undefined, numTrials, new HtmlReporter(), timeoutMs, evalId).catch(errorHandler);
71
+ const compareRefs = options.compareRef || [];
72
+ functionalCommand(selectedAgent, workspace, options.skill, maxAgents, undefined, numTrials, new HtmlReporter(), timeoutMs, evalId, compareRefs).catch(errorHandler);
69
73
  });
70
74
  const isMain = process.argv[1] && (() => {
71
75
  try {
@@ -32,7 +32,7 @@ function passColorClass(val) {
32
32
  return 'red';
33
33
  }
34
34
  function isFunctional(report) {
35
- return report.metrics.withoutSkillScore !== undefined;
35
+ return report.metrics.assertionPassRate['baseline'] !== undefined;
36
36
  }
37
37
  // ---------------------------------------------------------------------------
38
38
  // Metrics Grid
@@ -47,71 +47,44 @@ function renderDeltaCell(base, target, format, cellClass) {
47
47
  return `<span class="${cellClass} ${cls}">${sign}${pct}%</span>`;
48
48
  }
49
49
  function renderMetricsGrid(report) {
50
- const { metrics } = report;
50
+ const { metrics, results } = report;
51
51
  const functional = isFunctional(report);
52
- if (functional) {
53
- const bk = metrics.withoutSkillAssertionPassRate ?? metrics.withoutSkillPassAtK ?? 0;
54
- const tk = metrics.assertionPassRate ?? metrics.passAtK ?? 0;
55
- const upliftRaw = parseInt(metrics.skillUplift ?? '0', 10);
56
- const upliftClass = upliftRaw > 0 ? 'green' : upliftRaw < 0 ? 'red' : '';
57
- const wo = metrics.tokenStats?.withoutSkill;
58
- const wi = metrics.tokenStats?.withSkill;
59
- const wod = metrics.durationStats?.withoutSkill;
60
- const wid = metrics.durationStats?.withSkill;
61
- return `<div class="metrics-grid">
52
+ const skillVersions = results.length > 0 ? Object.keys(results[0].skillTrials) : ['local'];
53
+ const allVersions = functional ? ['baseline', ...skillVersions] : skillVersions;
54
+ const headerCells = allVersions.map(v => `<th>${v}</th>`).join('');
55
+ const successRows = `<tr>
56
+ <td>Success Rate</td>
57
+ ${allVersions.map(v => {
58
+ const val = metrics.assertionPassRate[v] ?? metrics.passAtK[v] ?? 0;
59
+ return `<td><span class="metric-val ${passColorClass(val)}">${formatPercent(val)}</span></td>`;
60
+ }).join('')}
61
+ </tr>`;
62
+ const tokenRows = `<tr>
63
+ <td>Tokens (avg)</td>
64
+ ${allVersions.map(v => {
65
+ const stats = metrics.tokenStats?.[v];
66
+ return `<td>${stats ? `<span class="metric-val">${formatTokens(stats.avgTotal)}</span><div class="metric-sub">avg total</div>` : '<span class="metric-val muted">—</span>'}</td>`;
67
+ }).join('')}
68
+ </tr>`;
69
+ const timeRows = `<tr>
70
+ <td>Time (avg)</td>
71
+ ${allVersions.map(v => {
72
+ const stats = metrics.durationStats?.[v];
73
+ return `<td>${stats ? `<span class="metric-val">${formatDuration(stats.avgMs)}</span>` : '<span class="metric-val muted">—</span>'}</td>`;
74
+ }).join('')}
75
+ </tr>`;
76
+ return `<div class="metrics-grid">
62
77
  <table>
63
78
  <thead>
64
- <tr><th></th><th>Without Skill</th><th>With Skill</th><th>Delta</th></tr>
79
+ <tr><th></th>${headerCells}</tr>
65
80
  </thead>
66
81
  <tbody>
67
- <tr>
68
- <td>Success Rate</td>
69
- <td><span class="metric-val ${passColorClass(bk)}">${formatPercent(bk)}</span></td>
70
- <td><span class="metric-val ${passColorClass(tk)}">${formatPercent(tk)}</span></td>
71
- <td><span class="metric-val ${upliftClass}">${escapeHtml(metrics.skillUplift ?? '0%')}</span></td>
72
- </tr>
73
- <tr>
74
- <td>Tokens (avg)</td>
75
- <td>${wo ? `<span class="metric-val">${formatTokens(wo.avgTotal)}</span><div class="metric-sub">avg total</div>` : '<span class="metric-val muted">—</span>'}</td>
76
- <td>${wi ? `<span class="metric-val">${formatTokens(wi.avgTotal)}</span><div class="metric-sub">avg total</div>` : '<span class="metric-val muted">—</span>'}</td>
77
- <td>${wo && wi ? renderDeltaCell(wo.avgTotal, wi.avgTotal, formatTokens, 'metric-val') : '<span class="metric-val muted">—</span>'}</td>
78
- </tr>
79
- <tr>
80
- <td>Time (avg)</td>
81
- <td>${wod ? `<span class="metric-val">${formatDuration(wod.avgMs)}</span>` : '<span class="metric-val muted">—</span>'}</td>
82
- <td>${wid ? `<span class="metric-val">${formatDuration(wid.avgMs)}</span>` : '<span class="metric-val muted">—</span>'}</td>
83
- <td>${wod && wid ? renderDeltaCell(wod.avgMs, wid.avgMs, formatDuration, 'metric-val') : '<span class="metric-val muted">—</span>'}</td>
84
- </tr>
82
+ ${successRows}
83
+ ${tokenRows}
84
+ ${timeRows}
85
85
  </tbody>
86
86
  </table>
87
87
  </div>`;
88
- }
89
- else {
90
- const k = metrics.passAtK ?? 0;
91
- const wi = metrics.tokenStats?.withSkill;
92
- const wid = metrics.durationStats?.withSkill;
93
- return `<div class="metrics-grid">
94
- <table>
95
- <thead>
96
- <tr><th></th><th>Score</th></tr>
97
- </thead>
98
- <tbody>
99
- <tr>
100
- <td>Success Rate</td>
101
- <td><span class="metric-val ${passColorClass(k)}">${formatPercent(k)}</span></td>
102
- </tr>
103
- <tr>
104
- <td>Tokens (avg)</td>
105
- <td>${wi ? `<span class="metric-val">${formatTokens(wi.avgTotal)}</span><div class="metric-sub">avg total</div>` : '<span class="metric-val muted">—</span>'}</td>
106
- </tr>
107
- <tr>
108
- <td>Time (avg)</td>
109
- <td>${wid ? `<span class="metric-val">${formatDuration(wid.avgMs)}</span>` : '<span class="metric-val muted">—</span>'}</td>
110
- </tr>
111
- </tbody>
112
- </table>
113
- </div>`;
114
- }
115
88
  }
116
89
  // ---------------------------------------------------------------------------
117
90
  // Trial details
@@ -160,63 +133,58 @@ function avgTrialDuration(trials) {
160
133
  return null;
161
134
  return Math.round(withDuration.reduce((s, t) => s + t.durationMs, 0) / withDuration.length);
162
135
  }
163
- function renderTaskMiniGrid(result) {
164
- const woTrials = result.withoutSkillTrials ?? [];
165
- const wiTrials = result.trials;
166
- const bk = woTrials.length ? Math.round(computeAssertionPassRate(woTrials) * 100) : 0;
167
- const tk = wiTrials.length ? Math.round(computeAssertionPassRate(wiTrials) * 100) : 0;
168
- const rateDelta = tk - bk;
169
- const rateDeltaSign = rateDelta >= 0 ? '+' : '';
170
- const rateDeltaClass = rateDelta > 0 ? 'green' : rateDelta < 0 ? 'red' : '';
171
- const woTokens = avgTrialTokens(woTrials);
172
- const wiTokens = avgTrialTokens(wiTrials);
173
- const woMs = avgTrialDuration(woTrials);
174
- const wiMs = avgTrialDuration(wiTrials);
175
- const tokenDeltaCell = (woTokens && wiTokens)
176
- ? renderDeltaCell(woTokens, wiTokens, formatTokens, 'metric-val-sm')
177
- : '<span class="metric-val-sm muted">—</span>';
178
- const durationDeltaCell = (woMs && wiMs)
179
- ? renderDeltaCell(woMs, wiMs, formatDuration, 'metric-val-sm')
180
- : '<span class="metric-val-sm muted">—</span>';
136
+ function renderTaskMiniGrid(result, isFunctionalEval) {
137
+ const skillVersions = Object.keys(result.skillTrials);
138
+ const allVersions = isFunctionalEval ? ['baseline', ...skillVersions] : skillVersions;
139
+ const headerCells = allVersions.map(v => `<th>${v}</th>`).join('');
140
+ const successRows = `<tr>
141
+ <td>Success Rate</td>
142
+ ${allVersions.map(v => {
143
+ const trials = v === 'baseline' ? result.baselineTrials : result.skillTrials[v];
144
+ const rate = trials.length ? Math.round(computeAssertionPassRate(trials) * 100) : 0;
145
+ return `<td><span class="metric-val-sm ${passColorClass(rate / 100)}">${rate}%</span></td>`;
146
+ }).join('')}
147
+ </tr>`;
148
+ const tokenRows = `<tr>
149
+ <td>Tokens (avg)</td>
150
+ ${allVersions.map(v => {
151
+ const trials = v === 'baseline' ? result.baselineTrials : result.skillTrials[v];
152
+ const tokens = avgTrialTokens(trials);
153
+ return `<td>${tokens != null ? `<span class="metric-val-sm">${formatTokens(tokens)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>`;
154
+ }).join('')}
155
+ </tr>`;
156
+ const timeRows = `<tr>
157
+ <td>Time (avg)</td>
158
+ ${allVersions.map(v => {
159
+ const trials = v === 'baseline' ? result.baselineTrials : result.skillTrials[v];
160
+ const ms = avgTrialDuration(trials);
161
+ return `<td>${ms != null ? `<span class="metric-val-sm">${formatDuration(ms)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>`;
162
+ }).join('')}
163
+ </tr>`;
181
164
  return `<div class="metrics-grid-sm">
182
165
  <table>
183
166
  <thead>
184
- <tr><th></th><th>Without Skill</th><th>With Skill</th><th>Delta</th></tr>
167
+ <tr><th></th>${headerCells}</tr>
185
168
  </thead>
186
169
  <tbody>
187
- <tr>
188
- <td>Success Rate</td>
189
- <td><span class="metric-val-sm ${passColorClass(bk / 100)}">${bk}%</span></td>
190
- <td><span class="metric-val-sm ${passColorClass(tk / 100)}">${tk}%</span></td>
191
- <td><span class="metric-val-sm ${rateDeltaClass}">${rateDeltaSign}${rateDelta}%</span></td>
192
- </tr>
193
- <tr>
194
- <td>Tokens (avg)</td>
195
- <td>${woTokens != null ? `<span class="metric-val-sm">${formatTokens(woTokens)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>
196
- <td>${wiTokens != null ? `<span class="metric-val-sm">${formatTokens(wiTokens)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>
197
- <td>${tokenDeltaCell}</td>
198
- </tr>
199
- <tr>
200
- <td>Time (avg)</td>
201
- <td>${woMs != null ? `<span class="metric-val-sm">${formatDuration(woMs)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>
202
- <td>${wiMs != null ? `<span class="metric-val-sm">${formatDuration(wiMs)}</span>` : '<span class="metric-val-sm muted">—</span>'}</td>
203
- <td>${durationDeltaCell}</td>
204
- </tr>
170
+ ${successRows}
171
+ ${tokenRows}
172
+ ${timeRows}
205
173
  </tbody>
206
174
  </table>
207
175
  </div>`;
208
176
  }
209
177
  function renderTaskDetails(result, isFunctionalEval) {
210
178
  const sections = [];
211
- if (isFunctionalEval) {
212
- sections.push(renderTaskMiniGrid(result));
179
+ 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)));
213
183
  }
214
- if (isFunctionalEval && result.withoutSkillTrials && result.withoutSkillTrials.length > 0) {
215
- sections.push('<div class="trial-group-label">Without Skill</div>');
216
- sections.push(...result.withoutSkillTrials.map(t => renderTrial(t)));
217
- sections.push('<div class="trial-group-label">With Skill</div>');
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)));
218
187
  }
219
- sections.push(...result.trials.map(t => renderTrial(t)));
220
188
  return `<div class="task-details" id="details-${result.taskId}">${sections.join('')}</div>`;
221
189
  }
222
190
  // ---------------------------------------------------------------------------
@@ -241,7 +209,7 @@ export function generateHtml(report) {
241
209
  const { skill_name, agent, timestamp, metrics } = report;
242
210
  const functional = isFunctional(report);
243
211
  const evalType = functional ? 'Functional' : 'Trigger';
244
- const overallScore = metrics.passAtK ?? 0;
212
+ const overallScore = metrics.passAtK['local'] ?? 0;
245
213
  const statusClass = passColorClass(overallScore);
246
214
  const formattedDate = new Date(timestamp).toLocaleString();
247
215
  return `<!DOCTYPE html>
@@ -1,9 +1,16 @@
1
1
  import { execSync, spawnSync } from 'child_process';
2
2
  export const executor = {
3
3
  execSync(command, options) {
4
- return execSync(command, options);
4
+ return execSync(command, {
5
+ ...options,
6
+ shell: process.env.SHELL || '/bin/sh',
7
+ env: { ...process.env, ...options?.env }
8
+ });
5
9
  },
6
10
  spawnSync(command, args, options) {
7
- return spawnSync(command, args, options);
11
+ return spawnSync(command, args, {
12
+ ...options,
13
+ env: { ...process.env, ...options?.env }
14
+ });
8
15
  }
9
16
  };
@@ -0,0 +1,30 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { executor } from './exec.js';
4
+ import { ExecutionError } from '../core/errors.js';
5
+ export const git = {
6
+ extractSkillRef(skillPath, ref, targetDir) {
7
+ // 1. Identify repo root
8
+ let repoRoot;
9
+ try {
10
+ repoRoot = executor.execSync('git rev-parse --show-toplevel', { cwd: skillPath }).toString().trim();
11
+ }
12
+ catch (err) {
13
+ throw new ExecutionError(`Path is not inside a git repository: ${skillPath}`);
14
+ }
15
+ // 2. Resolve relative path of skill within repo
16
+ const relativeSkillPath = path.relative(repoRoot, path.resolve(skillPath));
17
+ // 3. Create target directory
18
+ fs.mkdirSync(targetDir, { recursive: true });
19
+ // 4. Extract via git archive
20
+ try {
21
+ const cmd = `git archive ${ref} | tar -x -C ${targetDir}`;
22
+ executor.execSync(cmd, { cwd: repoRoot });
23
+ }
24
+ catch (err) {
25
+ throw new ExecutionError(`Failed to extract git reference '${ref}': ${err instanceof Error ? err.message : String(err)}`);
26
+ }
27
+ }
28
+ };
29
+ // Also keep named export for backward compatibility if needed
30
+ export const extractSkillRef = git.extractSkillRef;
@@ -126,42 +126,60 @@ export function renderRunHeader(config) {
126
126
  export function renderTriggerTable(report) {
127
127
  const { results, metrics } = report;
128
128
  const numTrials = metrics.numTrials || 1;
129
- const tableData = numTrials > 1
130
- ? [['ID', 'Prompt', 'Trials', 'success rate']]
131
- : [['ID', 'Prompt', 'success rate']];
129
+ // Identify all skill versions present in the results
130
+ const skillVersions = results.length > 0 ? Object.keys(results[0].skillTrials) : ['local'];
131
+ const header = ['ID', 'Prompt'];
132
+ if (numTrials > 1) {
133
+ for (const version of skillVersions) {
134
+ header.push(`${version} Trials`, `${version} Rate`);
135
+ }
136
+ }
137
+ else {
138
+ for (const version of skillVersions) {
139
+ header.push(`${version} Rate`);
140
+ }
141
+ }
142
+ const tableData = [header];
132
143
  let hasPartialErrors = false;
133
144
  for (const result of results) {
134
145
  const promptSnippet = result.prompt.substring(0, 40) + (result.prompt.length > 40 ? '...' : '');
135
- const p1Cell = formatPassAt1(result.trials);
136
- const someError = result.trials.some(t => t.isError);
137
- const allError = result.trials.length > 0 && result.trials.every(t => t.isError);
138
- if (someError && !allError)
139
- hasPartialErrors = true;
140
- if (numTrials > 1) {
141
- const errorCount = result.trials.filter(t => t.isError).length;
142
- const passedCount = result.trials.filter(t => t.trialPassed).length;
143
- const trialsBase = `${passedCount}/${result.trials.length}`;
144
- const trialsStr = errorCount > 0 ? `${trialsBase} (${errorCount}!)` : trialsBase;
145
- const trials = result.score === 1.0 ? chalk.green(trialsStr) : errorCount > 0 ? chalk.yellow(trialsStr) : chalk.red(trialsStr);
146
- tableData.push([result.taskId.toString(), promptSnippet, trials, p1Cell]);
147
- }
148
- else {
149
- tableData.push([result.taskId.toString(), promptSnippet, p1Cell]);
146
+ const row = [result.taskId.toString(), promptSnippet];
147
+ for (const version of skillVersions) {
148
+ const trials = result.skillTrials[version] || [];
149
+ const p1Cell = formatPassAt1(trials);
150
+ const someError = trials.some(t => t.isError);
151
+ const allError = trials.length > 0 && trials.every(t => t.isError);
152
+ if (someError && !allError)
153
+ hasPartialErrors = true;
154
+ if (numTrials > 1) {
155
+ const errorCount = trials.filter(t => t.isError).length;
156
+ const passedCount = trials.filter(t => t.trialPassed).length;
157
+ const trialsBase = `${passedCount}/${trials.length}`;
158
+ const trialsStr = errorCount > 0 ? `${trialsBase} (${errorCount}!)` : trialsBase;
159
+ const trialsCell = passedCount === trials.length ? chalk.green(trialsStr) : errorCount > 0 ? chalk.yellow(trialsStr) : chalk.red(trialsStr);
160
+ row.push(trialsCell, p1Cell);
161
+ }
162
+ else {
163
+ row.push(p1Cell);
164
+ }
150
165
  }
166
+ tableData.push(row);
151
167
  }
152
168
  Logger.table(tableData);
153
169
  if (hasPartialErrors) {
154
170
  Logger.write(chalk.yellow('\n * Some trials did not complete due to infrastructure errors. success rate is computed over the trials that ran.'));
155
171
  }
156
- const percentage = Math.round((metrics.passAtK || 0) * 100);
157
- Logger.write(`\n Trigger Success Rate: ${percentage}%`);
158
- const withSkillTokenStats = metrics.tokenStats?.withSkill;
159
- if (withSkillTokenStats) {
160
- Logger.write(`\n Avg Tokens: ${formatTokenStatsLine(withSkillTokenStats)}`);
161
- }
162
- const withSkillDurationStats = metrics.durationStats?.withSkill;
163
- if (withSkillDurationStats) {
164
- Logger.write(`\n Avg Time: ${formatDuration(withSkillDurationStats.avgMs)}`);
172
+ for (const version of skillVersions) {
173
+ const percentage = Math.round((metrics.passAtK[version] || 0) * 100);
174
+ Logger.write(`\n ${version} Success Rate: ${percentage}%`);
175
+ const tokenStats = metrics.tokenStats?.[version];
176
+ if (tokenStats) {
177
+ Logger.write(`\n Avg Tokens (${version}): ${formatTokenStatsLine(tokenStats)}`);
178
+ }
179
+ const durationStats = metrics.durationStats?.[version];
180
+ if (durationStats) {
181
+ Logger.write(`\n Avg Time (${version}): ${formatDuration(durationStats.avgMs)}`);
182
+ }
165
183
  }
166
184
  }
167
185
  /**
@@ -169,61 +187,46 @@ export function renderTriggerTable(report) {
169
187
  */
170
188
  export function renderFunctionalTable(report) {
171
189
  const { results, metrics } = report;
172
- const tableData = [['ID', 'Prompt', 'Without Skill', 'With Skill']];
190
+ // Identify all versions present (baseline + skill versions)
191
+ const skillVersions = results.length > 0 ? Object.keys(results[0].skillTrials) : ['local'];
192
+ const allVersions = ['baseline', ...skillVersions];
193
+ const header = ['ID', 'Prompt'];
194
+ for (const version of allVersions) {
195
+ header.push(version);
196
+ }
197
+ const tableData = [header];
173
198
  let hasPartialErrors = false;
174
199
  for (const result of results) {
175
200
  const promptSnippet = result.prompt.substring(0, 40) + (result.prompt.length > 40 ? '...' : '');
176
- const withoutSkillTrials = result.withoutSkillTrials || [];
177
- const withSkillTrials = result.trials;
178
- const baselineSomeError = withoutSkillTrials.some(t => t.isError);
179
- const baselineAllError = withoutSkillTrials.length > 0 && withoutSkillTrials.every(t => t.isError);
180
- const withSkillSomeError = withSkillTrials.some(t => t.isError);
181
- const withSkillAllError = withSkillTrials.length > 0 && withSkillTrials.every(t => t.isError);
182
- if ((baselineSomeError && !baselineAllError) || (withSkillSomeError && !withSkillAllError))
201
+ const row = [result.taskId.toString(), promptSnippet];
202
+ // Baseline
203
+ const woTrials = result.baselineTrials || [];
204
+ if (woTrials.some(t => t.isError) && !woTrials.every(t => t.isError))
183
205
  hasPartialErrors = true;
184
- tableData.push([
185
- result.taskId.toString(),
186
- promptSnippet,
187
- formatAssertionRate(withoutSkillTrials),
188
- formatAssertionRate(withSkillTrials),
189
- ]);
206
+ row.push(formatAssertionRate(woTrials));
207
+ // Skills
208
+ for (const version of skillVersions) {
209
+ const wiTrials = result.skillTrials[version] || [];
210
+ if (wiTrials.some(t => t.isError) && !wiTrials.every(t => t.isError))
211
+ hasPartialErrors = true;
212
+ row.push(formatAssertionRate(wiTrials));
213
+ }
214
+ tableData.push(row);
190
215
  }
191
216
  Logger.table(tableData);
192
217
  if (hasPartialErrors) {
193
218
  Logger.write(chalk.yellow('\n * Some trials did not complete due to infrastructure errors. success rate is computed over the trials that ran.'));
194
219
  }
195
- const withoutSkillPercentage = Math.round(((metrics.withoutSkillAssertionPassRate ?? metrics.withoutSkillPassAtK) || 0) * 100);
196
- const withSkillPercentage = Math.round(((metrics.assertionPassRate ?? metrics.passAtK) || 0) * 100);
197
- Logger.write(`\n Without Skill Rate: ${withoutSkillPercentage}%`);
198
- Logger.write(`\n With Skill Rate: ${withSkillPercentage}%`);
199
- const baselineTokenStats = metrics.tokenStats?.withoutSkill;
200
- const withSkillTokenStats = metrics.tokenStats?.withSkill;
201
- if (baselineTokenStats || withSkillTokenStats) {
202
- if (baselineTokenStats)
203
- Logger.write(`\n Tokens (w/o skill): ${formatTokenStatsLine(baselineTokenStats)}`);
204
- if (withSkillTokenStats)
205
- Logger.write(`\n Tokens (w/ skill): ${formatTokenStatsLine(withSkillTokenStats)}`);
206
- if (baselineTokenStats && withSkillTokenStats && baselineTokenStats.avgTotal > 0) {
207
- const delta = withSkillTokenStats.avgTotal - baselineTokenStats.avgTotal;
208
- const deltaSign = delta >= 0 ? '+' : '';
209
- const deltaPct = Math.round((delta / baselineTokenStats.avgTotal) * 100);
210
- const deltaStr = `${deltaSign}${formatTokens(Math.abs(delta))} (${deltaSign}${deltaPct}%)`;
211
- Logger.write(`\n Token Delta: ${delta >= 0 ? chalk.yellow(deltaStr) : chalk.green(deltaStr)}`);
220
+ for (const version of allVersions) {
221
+ const rate = Math.round(((metrics.assertionPassRate[version] ?? metrics.passAtK[version]) || 0) * 100);
222
+ Logger.write(`\n ${version} Rate: ${rate}%`);
223
+ const stats = metrics.tokenStats?.[version];
224
+ if (stats) {
225
+ Logger.write(`\n Tokens (${version}): ${formatTokenStatsLine(stats)}`);
212
226
  }
213
- }
214
- const baselineDurationStats = metrics.durationStats?.withoutSkill;
215
- const withSkillDurationStats = metrics.durationStats?.withSkill;
216
- if (baselineDurationStats || withSkillDurationStats) {
217
- if (baselineDurationStats)
218
- Logger.write(`\n Time (w/o skill): ${formatDuration(baselineDurationStats.avgMs)} avg`);
219
- if (withSkillDurationStats)
220
- Logger.write(`\n Time (w/ skill): ${formatDuration(withSkillDurationStats.avgMs)} avg`);
221
- if (baselineDurationStats && withSkillDurationStats && baselineDurationStats.avgMs > 0) {
222
- const delta = withSkillDurationStats.avgMs - baselineDurationStats.avgMs;
223
- const deltaSign = delta >= 0 ? '+' : '';
224
- const deltaPct = Math.round((delta / baselineDurationStats.avgMs) * 100);
225
- const deltaStr = `${deltaSign}${formatDuration(Math.abs(delta))} (${deltaSign}${deltaPct}%)`;
226
- Logger.write(`\n Time Delta: ${delta >= 0 ? chalk.yellow(deltaStr) : chalk.green(deltaStr)}`);
227
+ const dStats = metrics.durationStats?.[version];
228
+ if (dStats) {
229
+ Logger.write(`\n Time (${version}): ${formatDuration(dStats.avgMs)} avg`);
227
230
  }
228
231
  }
229
232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fede0089/skill-eval",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "CLI to evaluate agent skills triggering",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -12,6 +12,9 @@
12
12
  "LICENSE"
13
13
  ],
14
14
  "scripts": {
15
+ "release:patch": "npm version patch && git push --follow-tags",
16
+ "release:minor": "npm version minor && git push --follow-tags",
17
+ "release:major": "npm version major && git push --follow-tags",
15
18
  "prepare": "npm run build",
16
19
  "build": "tsc",
17
20
  "start": "node dist/index.js",
@@ -1,20 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.rateCommand = rateCommand;
4
- const logger_1 = require("../utils/logger");
5
- const errors_1 = require("../core/errors");
6
- async function rateCommand(score, options) {
7
- const rating = parseInt(score, 10);
8
- if (isNaN(rating) || rating < 1 || rating > 5) {
9
- throw new errors_1.ValidationError('Rating must be a number between 1 and 5.');
10
- }
11
- logger_1.Logger.info('\n==========================================');
12
- logger_1.Logger.info(' EXPERIENCE RATING VIEW ');
13
- logger_1.Logger.info('==========================================');
14
- logger_1.Logger.info(`Rating: ${'★'.repeat(rating)}${'☆'.repeat(5 - rating)}`);
15
- if (options.comment) {
16
- logger_1.Logger.info(`Comment: ${options.comment}`);
17
- }
18
- logger_1.Logger.info('==========================================');
19
- logger_1.Logger.info('Thank you for your feedback!\n');
20
- }
@@ -1,43 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import chalk from 'chalk';
4
- import { Logger } from '../utils/logger.js';
5
- import { AppError } from '../core/errors.js';
6
- import { renderTriggerTable, renderFunctionalTable } from '../utils/table-renderer.js';
7
- import { JsonReporter } from '../core/reporters/index.js';
8
- export async function showCommand(workspace, reporter = new JsonReporter()) {
9
- const runsDir = path.resolve(workspace, '.project-skill-evals', 'runs');
10
- if (!fs.existsSync(runsDir)) {
11
- throw new AppError('No evaluation runs found. Run an evaluation first.');
12
- }
13
- const runs = fs.readdirSync(runsDir)
14
- .filter(dir => fs.statSync(path.join(runsDir, dir)).isDirectory())
15
- .sort((a, b) => b.localeCompare(a)); // Sort descending to get latest
16
- if (runs.length === 0) {
17
- throw new AppError('No evaluation runs found. Run an evaluation first.');
18
- }
19
- const latestRun = runs[0];
20
- const summaryPath = path.join(runsDir, latestRun, 'summary.json');
21
- if (!fs.existsSync(summaryPath)) {
22
- throw new AppError(`Summary file not found for the latest run: ${latestRun}`);
23
- }
24
- const report = JSON.parse(fs.readFileSync(summaryPath, 'utf-8'));
25
- const { skill_name, agent, metrics, results, timestamp } = report;
26
- const numTrials = metrics.numTrials || 1;
27
- const isFunctional = metrics.withoutSkillScore !== undefined;
28
- Logger.write(`\n${chalk.bold('LATEST EVALUATION RESULTS')}\n`);
29
- Logger.write(`Timestamp: ${new Date(timestamp).toLocaleString()}\n`);
30
- Logger.write(`Skill: ${skill_name}\n`);
31
- Logger.write(`Agent: ${agent}\n`);
32
- Logger.write(`Type: ${isFunctional ? 'Functional' : 'Trigger'}\n`);
33
- Logger.write(`──────────────────────────────────────────────────\n`);
34
- if (isFunctional) {
35
- renderFunctionalTable(report);
36
- }
37
- else {
38
- renderTriggerTable(report);
39
- }
40
- Logger.write('\n');
41
- const runDir = path.join(runsDir, latestRun);
42
- reporter.generate(report, runDir);
43
- }
@@ -1,30 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.viewCommand = viewCommand;
4
- const logger_1 = require("../utils/logger");
5
- async function viewCommand(options) {
6
- const ease = parseInt(options.ease || '0', 10);
7
- const speed = parseInt(options.speed || '0', 10);
8
- const accuracy = parseInt(options.accuracy || '0', 10);
9
- const drawStars = (score) => {
10
- const stars = '★'.repeat(score);
11
- const empty = '☆'.repeat(5 - score);
12
- return `[ ${stars}${empty} ]`;
13
- };
14
- logger_1.Logger.info('\n┌──────────────────────────────────────────┐');
15
- logger_1.Logger.info('│ NUEVA VISTA DE CALIFICACIÓN │');
16
- logger_1.Logger.info('├──────────────────────────────────────────┤');
17
- logger_1.Logger.info(`│ Facilidad de uso: ${drawStars(ease)} │`);
18
- logger_1.Logger.info(`│ Velocidad: ${drawStars(speed)} │`);
19
- logger_1.Logger.info(`│ Precisión: ${drawStars(accuracy)} │`);
20
- logger_1.Logger.info('├──────────────────────────────────────────┤');
21
- if (options.comment) {
22
- logger_1.Logger.info(`│ Comentario: │`);
23
- // Basic wrapping for the comment
24
- const comment = options.comment.substring(0, 38);
25
- logger_1.Logger.info(`│ ${comment.padEnd(40)} │`);
26
- }
27
- logger_1.Logger.info('├──────────────────────────────────────────┤');
28
- logger_1.Logger.info('│ ¡Gracias por calificar tu experiencia! │');
29
- logger_1.Logger.info('└──────────────────────────────────────────┘\n');
30
- }