@fede0089/skill-eval 3.1.0 → 3.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.
- package/README.md +44 -7
- package/dist/commands/functional.js +10 -10
- package/dist/commands/trigger.js +6 -9
- package/dist/core/anomalies.js +63 -0
- package/dist/core/eval-runner.js +28 -12
- package/dist/core/statistics.js +11 -8
- package/dist/core/trial-utils.js +45 -1
- package/dist/index.js +20 -6
- package/dist/reporters/html-reporter.js +970 -324
- package/dist/reporters/index.js +0 -1
- package/dist/utils/eval-loader.js +20 -3
- package/dist/utils/ndjson.js +19 -0
- package/dist/utils/table-renderer.js +7 -2
- package/package.json +1 -1
- package/dist/reporters/json-reporter.js +0 -10
package/dist/reporters/index.js
CHANGED
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { ConfigError } from '../core/errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes an --eval-file value to the bare file name used inside the evals directory.
|
|
6
|
+
* Accepts 'edge-cases', 'edge-cases.json' and 'path/to/evals/edge-cases.json' alike.
|
|
7
|
+
*/
|
|
8
|
+
function normalizeEvalFileName(evalFile) {
|
|
9
|
+
const base = path.basename(evalFile.trim());
|
|
10
|
+
return base.endsWith('.json') ? base : `${base}.json`;
|
|
11
|
+
}
|
|
4
12
|
/**
|
|
5
13
|
* Loads and merges all JSON evaluation files from a skill's evals directory.
|
|
6
14
|
* Aligned with Anthropic's recommendation to split evals by capability/regression.
|
|
7
15
|
* Supports legacy 'tasks' and 'assertions' internally while maintaining 'evals' and 'expectations' in files.
|
|
16
|
+
* When 'evalFile' is given, only that file is loaded instead of the whole suite.
|
|
8
17
|
*/
|
|
9
|
-
export function loadEvalSuite(skillPath) {
|
|
18
|
+
export function loadEvalSuite(skillPath, evalFile) {
|
|
10
19
|
const evalsDir = path.resolve(skillPath, 'evals');
|
|
11
20
|
if (!fs.existsSync(evalsDir)) {
|
|
12
21
|
throw new ConfigError(`Could not find evals directory at ${evalsDir}`);
|
|
13
22
|
}
|
|
14
|
-
|
|
23
|
+
let files = fs.readdirSync(evalsDir).filter(file => file.endsWith('.json'));
|
|
15
24
|
if (files.length === 0) {
|
|
16
25
|
throw new ConfigError(`No JSON evaluation files found in ${evalsDir}`);
|
|
17
26
|
}
|
|
27
|
+
if (evalFile !== undefined) {
|
|
28
|
+
const wanted = normalizeEvalFileName(evalFile);
|
|
29
|
+
if (!files.includes(wanted)) {
|
|
30
|
+
throw new ConfigError(`Eval file '${wanted}' not found in ${evalsDir}. Available: ${[...files].sort().join(', ')}.`);
|
|
31
|
+
}
|
|
32
|
+
files = [wanted];
|
|
33
|
+
}
|
|
18
34
|
let mergedSkillName = '';
|
|
19
35
|
const mergedTasks = [];
|
|
20
36
|
for (const file of files) {
|
|
@@ -59,7 +75,8 @@ export function loadEvalSuite(skillPath) {
|
|
|
59
75
|
mergedTasks.push(...mappedTasks);
|
|
60
76
|
}
|
|
61
77
|
if (mergedTasks.length === 0) {
|
|
62
|
-
|
|
78
|
+
const scope = evalFile !== undefined ? `${files[0]} in ${evalsDir}` : `any of the JSON files in ${evalsDir}`;
|
|
79
|
+
throw new ConfigError(`No evaluations found in ${scope}`);
|
|
63
80
|
}
|
|
64
81
|
return {
|
|
65
82
|
skill_name: mergedSkillName,
|
package/dist/utils/ndjson.js
CHANGED
|
@@ -62,6 +62,25 @@ export function parseStreamResult(output) {
|
|
|
62
62
|
(typeof resultEvent.response === 'string' ? resultEvent.response : '');
|
|
63
63
|
return { response: text };
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Counts tool invocations and reads the terminal status from a stream-json blob.
|
|
67
|
+
* Both are needed to tell a legitimate short answer apart from an agent that
|
|
68
|
+
* stopped early or degenerated: a trial with tool calls and a 'success' status
|
|
69
|
+
* that still produced two characters of text is a very different failure from
|
|
70
|
+
* one that never started.
|
|
71
|
+
* Returns toolCalls: 0 and an undefined status when the blob carries no events.
|
|
72
|
+
*/
|
|
73
|
+
export function parseStreamStats(output) {
|
|
74
|
+
let toolCalls = 0;
|
|
75
|
+
let status;
|
|
76
|
+
for (const event of parseNdjsonEvents(output)) {
|
|
77
|
+
if (event.type === 'tool_use')
|
|
78
|
+
toolCalls += 1;
|
|
79
|
+
else if (event.type === 'result')
|
|
80
|
+
status = event.status;
|
|
81
|
+
}
|
|
82
|
+
return { toolCalls, status };
|
|
83
|
+
}
|
|
65
84
|
/**
|
|
66
85
|
* Extracts token consumption stats from a Gemini CLI stream-json stdout blob.
|
|
67
86
|
* Looks for a result event with a stats.total_tokens field.
|
|
@@ -98,7 +98,7 @@ function boxLabel(key, value) {
|
|
|
98
98
|
* Always shown regardless of debug mode.
|
|
99
99
|
*/
|
|
100
100
|
export function renderRunHeader(config) {
|
|
101
|
-
const { command, skillName, agent, workspace, tasks, trials, maxAgents, timeoutMs, runDir, evalId } = config;
|
|
101
|
+
const { command, skillName, agent, workspace, tasks, trials, maxAgents, timeoutMs, runDir, evalId, evalFile } = config;
|
|
102
102
|
let timeoutStr = 'None';
|
|
103
103
|
if (timeoutMs && timeoutMs > 0) {
|
|
104
104
|
const timeoutSec = timeoutMs / 1000;
|
|
@@ -111,7 +111,12 @@ export function renderRunHeader(config) {
|
|
|
111
111
|
const dashes = '─'.repeat(BOX_INNER - titleLabel.length);
|
|
112
112
|
const top = chalk.gray('┌─ ') + chalk.bold(titleLabel) + ' ' + chalk.gray(dashes + '┐');
|
|
113
113
|
const bottom = chalk.gray('└' + '─'.repeat(BOX_INNER + 2) + '┘');
|
|
114
|
-
const
|
|
114
|
+
const filterParts = [];
|
|
115
|
+
if (evalFile !== undefined)
|
|
116
|
+
filterParts.push(evalFile);
|
|
117
|
+
if (evalId !== undefined)
|
|
118
|
+
filterParts.push(`eval #${evalId}`);
|
|
119
|
+
const commandPart = [command, ...filterParts].join(' · ');
|
|
115
120
|
const title = chalk.bold.cyan(skillName) + chalk.gray(` · ${commandPart}`);
|
|
116
121
|
const runLine = `${tasks} task${tasks !== 1 ? 's' : ''} · ${trials} trial${trials !== 1 ? 's' : ''} · agents ${maxAgents}`;
|
|
117
122
|
process.stdout.write('\n');
|
package/package.json
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { Logger } from '../utils/logger.js';
|
|
4
|
-
export class JsonReporter {
|
|
5
|
-
generate(report, runDir) {
|
|
6
|
-
const jsonPath = path.join(runDir, 'summary.json');
|
|
7
|
-
fs.writeFileSync(jsonPath, JSON.stringify(report, null, 2), 'utf-8');
|
|
8
|
-
Logger.write(`\n Report: file://${jsonPath}\n`);
|
|
9
|
-
}
|
|
10
|
-
}
|