@wix/pathgrade 1.0.27 → 1.0.28
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 +12 -19
- package/dist/adapters/jest/invocation-adapter.js +5 -1
- package/dist/adapters/jest/reporter.js +4 -1
- package/dist/adapters/jest/results.js +8 -0
- package/dist/adapters/node-test/index.d.ts +5 -0
- package/dist/adapters/node-test/index.js +35 -7
- package/dist/adapters/node-test/invocation-adapter.js +3 -1
- package/dist/adapters/node-test/runner-adapter.js +22 -15
- package/dist/adapters/vitest/reporter.js +4 -1
- package/dist/agents/codex-app-server/agent.js +1 -51
- package/dist/agents/codex-app-server/turn-notifications.d.ts +6 -0
- package/dist/agents/codex-app-server/turn-notifications.js +51 -0
- package/dist/agents/codex-app-server/turn-state.d.ts +19 -0
- package/dist/agents/codex-app-server/turn-state.js +1 -0
- package/dist/agents/opencode/protocol.d.ts +7 -0
- package/dist/agents/opencode/protocol.js +47 -0
- package/dist/agents/opencode.js +1 -47
- package/dist/analytics/engine.js +5 -2
- package/dist/commands/report.js +16 -2
- package/dist/commands/run-args.d.ts +1 -0
- package/dist/commands/run-args.js +13 -0
- package/dist/commands/run-changed.js +5 -15
- package/dist/config/pathgrade.d.ts +3 -0
- package/dist/config/pathgrade.js +33 -1
- package/dist/pathgrade.js +10 -2
- package/dist/reporters/cli.js +13 -6
- package/dist/reporters/github-comment.js +12 -9
- package/dist/reporters/loader.d.ts +1 -0
- package/dist/reporters/loader.js +27 -2
- package/dist/reporters/report-summary.js +13 -5
- package/dist/reporting/core.d.ts +1 -0
- package/dist/reporting/core.js +183 -105
- package/dist/reporting/types.d.ts +19 -3
- package/dist/runners/adapter-loader.js +17 -12
- package/dist/runners/direct-reporter-attempts.d.ts +1 -0
- package/dist/runners/direct-reporter-attempts.js +7 -0
- package/dist/runners/invocation.d.ts +2 -0
- package/dist/runners/model-builders.js +1 -0
- package/dist/runners/model-validation.js +31 -0
- package/dist/runners/model.d.ts +4 -0
- package/dist/runners/orchestrator.d.ts +2 -0
- package/dist/runners/orchestrator.js +11 -1
- package/dist/runners/repeated-attempts.d.ts +7 -0
- package/dist/runners/repeated-attempts.js +149 -0
- package/dist/runners/repeated-invocation.d.ts +7 -0
- package/dist/runners/repeated-invocation.js +129 -0
- package/dist/runners/report-projection.js +16 -6
- package/dist/runners/vitest-adapter.js +10 -0
- package/dist/runners/vitest-invocation.js +2 -0
- package/dist/sdk/agent-runtime-options.d.ts +12 -0
- package/dist/sdk/agent-runtime-options.js +67 -0
- package/dist/sdk/agent.js +4 -57
- package/dist/sdk/case-context.js +7 -2
- package/dist/sdk/lifecycle.js +8 -3
- package/dist/sdk/result-capture.js +4 -1
- package/dist/types.d.ts +32 -5
- package/dist/viewer.html +19 -19
- package/package.json +2 -2
package/dist/commands/report.js
CHANGED
|
@@ -43,10 +43,24 @@ function isPathgradeReport(value) {
|
|
|
43
43
|
if (!value || typeof value !== 'object')
|
|
44
44
|
return false;
|
|
45
45
|
const v = value;
|
|
46
|
-
return (v.version === 1 &&
|
|
46
|
+
return ((v.version === 1 || v.version === 2) &&
|
|
47
47
|
typeof v.overall_pass_rate === 'number' &&
|
|
48
48
|
(v.status === 'pass' || v.status === 'fail') &&
|
|
49
|
-
Array.isArray(v.groups)
|
|
49
|
+
Array.isArray(v.groups) &&
|
|
50
|
+
(v.version === 1 || (typeof v.overall_mean_reward === 'number'
|
|
51
|
+
&& hasValidAttemptCounts(v)
|
|
52
|
+
&& v.groups.every(group => typeof group.mean_reward === 'number'))));
|
|
53
|
+
}
|
|
54
|
+
function hasValidAttemptCounts(report) {
|
|
55
|
+
const requested = report.attempts_requested;
|
|
56
|
+
const completed = report.attempts_completed;
|
|
57
|
+
return typeof requested === 'number'
|
|
58
|
+
&& Number.isSafeInteger(requested)
|
|
59
|
+
&& requested >= 1
|
|
60
|
+
&& typeof completed === 'number'
|
|
61
|
+
&& Number.isSafeInteger(completed)
|
|
62
|
+
&& completed >= 0
|
|
63
|
+
&& completed <= requested;
|
|
50
64
|
}
|
|
51
65
|
async function loadReport(resolvedPath) {
|
|
52
66
|
if (!(await fs.pathExists(resolvedPath))) {
|
|
@@ -14,6 +14,7 @@ export function parsePathgradeRunArgs(args) {
|
|
|
14
14
|
let since;
|
|
15
15
|
let changedFilesPath;
|
|
16
16
|
let adapterName;
|
|
17
|
+
let attempts;
|
|
17
18
|
let passthrough = false;
|
|
18
19
|
for (const arg of args) {
|
|
19
20
|
if (passthrough) {
|
|
@@ -44,6 +45,10 @@ export function parsePathgradeRunArgs(args) {
|
|
|
44
45
|
adapterName = arg.slice('--adapter='.length);
|
|
45
46
|
continue;
|
|
46
47
|
}
|
|
48
|
+
if (arg.startsWith('--attempts=')) {
|
|
49
|
+
attempts = parseAttempts(arg.slice('--attempts='.length));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
47
52
|
if (arg.startsWith('--since=')) {
|
|
48
53
|
since = arg.slice('--since='.length);
|
|
49
54
|
continue;
|
|
@@ -68,8 +73,16 @@ export function parsePathgradeRunArgs(args) {
|
|
|
68
73
|
changed,
|
|
69
74
|
quiet,
|
|
70
75
|
adapterName,
|
|
76
|
+
attempts,
|
|
71
77
|
since,
|
|
72
78
|
changedFilesPath,
|
|
73
79
|
};
|
|
74
80
|
return warnings.length > 0 ? { ...base, warnings } : base;
|
|
75
81
|
}
|
|
82
|
+
function parseAttempts(value) {
|
|
83
|
+
const attempts = Number(value);
|
|
84
|
+
if (!Number.isSafeInteger(attempts) || attempts < 1) {
|
|
85
|
+
throw new Error('pathgrade: --attempts must be an integer greater than or equal to 1');
|
|
86
|
+
}
|
|
87
|
+
return attempts;
|
|
88
|
+
}
|
|
@@ -27,13 +27,13 @@ export async function runChanged(opts) {
|
|
|
27
27
|
const runnerEnv = buildRunnerEnv(parsed, {
|
|
28
28
|
PATHGRADE_SELECTION_INVOCATION_ID: selectionInvocationId,
|
|
29
29
|
});
|
|
30
|
-
const configPath = findVitestConfigArg(parsed.runnerArgs);
|
|
31
30
|
let config;
|
|
32
31
|
let runnerInvocation;
|
|
33
32
|
try {
|
|
34
33
|
config = await resolvePathgradeConfig({
|
|
35
34
|
cwd,
|
|
36
|
-
|
|
35
|
+
cli: parsed.attempts === undefined ? undefined : { attempts: parsed.attempts },
|
|
36
|
+
runnerArgs: parsed.runnerArgs,
|
|
37
37
|
warn: w => {
|
|
38
38
|
if (!parsed.quiet)
|
|
39
39
|
process.stderr.write(`${w}\n`);
|
|
@@ -107,6 +107,7 @@ export async function runChanged(opts) {
|
|
|
107
107
|
totalEvals: evalFiles.length,
|
|
108
108
|
changedCount: changedFiles.length,
|
|
109
109
|
result,
|
|
110
|
+
attempts: config.attempts,
|
|
110
111
|
});
|
|
111
112
|
}
|
|
112
113
|
// Persist the sidecar immediately — even on empty selection, so the
|
|
@@ -128,7 +129,7 @@ export async function runChanged(opts) {
|
|
|
128
129
|
cwd,
|
|
129
130
|
runnerArgs,
|
|
130
131
|
selectedFiles,
|
|
131
|
-
env: runnerEnv,
|
|
132
|
+
env: { ...runnerEnv, PATHGRADE_ATTEMPT_COUNT: String(config.attempts) },
|
|
132
133
|
});
|
|
133
134
|
}
|
|
134
135
|
function printRunStartSummary(input) {
|
|
@@ -140,6 +141,7 @@ function printRunStartSummary(input) {
|
|
|
140
141
|
const globalLabel = result.globalMatch ? `\`${result.globalMatch}\`` : 'none';
|
|
141
142
|
process.stderr.write(` global matches: ${globalLabel}\n`);
|
|
142
143
|
process.stderr.write(` selected: ${result.selected.length} / ${totalEvals} evals\n`);
|
|
144
|
+
process.stderr.write(` attempts: ${input.attempts} per selected case\n`);
|
|
143
145
|
for (const entry of result.selected) {
|
|
144
146
|
process.stderr.write(` ${entry.file}\n`);
|
|
145
147
|
}
|
|
@@ -158,15 +160,3 @@ function readChangedFilesList(filePath) {
|
|
|
158
160
|
function errMsg(err) {
|
|
159
161
|
return err instanceof Error ? err.message : String(err);
|
|
160
162
|
}
|
|
161
|
-
function findVitestConfigArg(args) {
|
|
162
|
-
for (let i = 0; i < args.length; i++) {
|
|
163
|
-
const arg = args[i];
|
|
164
|
-
if (arg === '--config' || arg === '-c')
|
|
165
|
-
return args[i + 1];
|
|
166
|
-
if (arg.startsWith('--config='))
|
|
167
|
-
return arg.slice('--config='.length);
|
|
168
|
-
if (arg.startsWith('-c='))
|
|
169
|
-
return arg.slice('-c='.length);
|
|
170
|
-
}
|
|
171
|
-
return undefined;
|
|
172
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export interface PathgradeConfig {
|
|
2
|
+
attempts?: number;
|
|
2
3
|
runner?: {
|
|
3
4
|
adapter?: string;
|
|
4
5
|
args?: string[];
|
|
@@ -18,6 +19,7 @@ export interface PathgradeConfig {
|
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
export interface ResolvedPathgradeConfig {
|
|
22
|
+
attempts: number;
|
|
21
23
|
runner: {
|
|
22
24
|
adapter: string;
|
|
23
25
|
args: string[];
|
|
@@ -44,5 +46,6 @@ export declare function resolvePathgradeConfig(input: {
|
|
|
44
46
|
cli?: PathgradeConfig;
|
|
45
47
|
configPath?: string;
|
|
46
48
|
legacyVitestConfigPath?: string;
|
|
49
|
+
runnerArgs?: readonly string[];
|
|
47
50
|
warn?: (message: string) => void;
|
|
48
51
|
}): Promise<ResolvedPathgradeConfig>;
|
package/dist/config/pathgrade.js
CHANGED
|
@@ -17,6 +17,7 @@ const PATHGRADE_CONFIG_CANDIDATES = [
|
|
|
17
17
|
];
|
|
18
18
|
export function defaultPathgradeConfig() {
|
|
19
19
|
return {
|
|
20
|
+
attempts: 1,
|
|
20
21
|
runner: {
|
|
21
22
|
adapter: 'vitest',
|
|
22
23
|
args: [],
|
|
@@ -35,13 +36,30 @@ export function defaultPathgradeConfig() {
|
|
|
35
36
|
}
|
|
36
37
|
export async function resolvePathgradeConfig(input) {
|
|
37
38
|
const fileConfig = await loadPathgradeConfigFile(input.cwd, input.configPath);
|
|
38
|
-
const legacyConfig = await loadLegacyVitestConfig(input.cwd, input.legacyVitestConfigPath
|
|
39
|
+
const legacyConfig = await loadLegacyVitestConfig(input.cwd, input.legacyVitestConfigPath ?? findVitestConfigArg([
|
|
40
|
+
...(fileConfig?.runner?.args ?? []),
|
|
41
|
+
...(input.runnerArgs ?? []),
|
|
42
|
+
]), input.warn);
|
|
39
43
|
return mergePathgradeConfig(mergePathgradeConfig(mergePathgradeConfig(defaultPathgradeConfig(), legacyConfig), fileConfig), input.cli);
|
|
40
44
|
}
|
|
45
|
+
function findVitestConfigArg(args) {
|
|
46
|
+
let configPath;
|
|
47
|
+
for (let i = 0; i < args.length; i++) {
|
|
48
|
+
const arg = args[i];
|
|
49
|
+
if (arg === '--config' || arg === '-c')
|
|
50
|
+
configPath = args[i + 1];
|
|
51
|
+
else if (arg.startsWith('--config='))
|
|
52
|
+
configPath = arg.slice('--config='.length);
|
|
53
|
+
else if (arg.startsWith('-c='))
|
|
54
|
+
configPath = arg.slice('-c='.length);
|
|
55
|
+
}
|
|
56
|
+
return configPath;
|
|
57
|
+
}
|
|
41
58
|
function mergePathgradeConfig(base, override) {
|
|
42
59
|
if (!override)
|
|
43
60
|
return base;
|
|
44
61
|
return {
|
|
62
|
+
attempts: override.attempts ?? base.attempts,
|
|
45
63
|
runner: {
|
|
46
64
|
adapter: override.runner?.adapter ?? base.runner.adapter,
|
|
47
65
|
args: override.runner?.args ?? base.runner.args,
|
|
@@ -91,6 +109,7 @@ function validatePathgradeConfig(value, label) {
|
|
|
91
109
|
throw invalidConfig(label, 'default export must be an object');
|
|
92
110
|
}
|
|
93
111
|
validateOptionalObject(value.runner, label, 'runner');
|
|
112
|
+
validateOptionalPositiveInteger(value.attempts, label, 'attempts');
|
|
94
113
|
const runner = asOptionalObject(value.runner);
|
|
95
114
|
validateOptionalString(runner?.adapter, label, 'runner.adapter');
|
|
96
115
|
validateOptionalStringArray(runner?.args, label, 'runner.args');
|
|
@@ -139,6 +158,11 @@ function validateOptionalNumber(value, label, field) {
|
|
|
139
158
|
throw invalidConfig(label, `${field} must be a number`);
|
|
140
159
|
}
|
|
141
160
|
}
|
|
161
|
+
function validateOptionalPositiveInteger(value, label, field) {
|
|
162
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || Number(value) < 1)) {
|
|
163
|
+
throw invalidConfig(label, `${field} must be an integer greater than or equal to 1`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
142
166
|
function invalidConfig(label, reason) {
|
|
143
167
|
return new InvalidPathgradeConfigError(`pathgrade: invalid ${label}: ${reason}`);
|
|
144
168
|
}
|
|
@@ -179,6 +203,14 @@ async function loadLegacyVitestConfig(cwd, configPath, warn = () => { }) {
|
|
|
179
203
|
if (!isObject(opts))
|
|
180
204
|
return undefined;
|
|
181
205
|
return {
|
|
206
|
+
...(opts.reporter === 'cli' || opts.reporter === 'browser' || opts.reporter === 'json'
|
|
207
|
+
? { reporter: opts.reporter }
|
|
208
|
+
: {}),
|
|
209
|
+
...(typeof opts.diagnostics === 'boolean' ? { diagnostics: opts.diagnostics } : {}),
|
|
210
|
+
...(typeof opts.verbose === 'boolean' ? { verbose: opts.verbose } : {}),
|
|
211
|
+
...(isObject(opts.ci) && typeof opts.ci.threshold === 'number'
|
|
212
|
+
? { ci: { threshold: opts.ci.threshold } }
|
|
213
|
+
: {}),
|
|
182
214
|
evals: {
|
|
183
215
|
...(Array.isArray(opts.include) ? { include: opts.include } : {}),
|
|
184
216
|
...(Array.isArray(opts.exclude) ? { exclude: opts.exclude } : {}),
|
package/dist/pathgrade.js
CHANGED
|
@@ -201,7 +201,14 @@ export async function runPathgradeCli(options = {}) {
|
|
|
201
201
|
await clearSidecar(process.cwd());
|
|
202
202
|
const env = buildRunnerEnv(parsed);
|
|
203
203
|
try {
|
|
204
|
-
const config = await resolvePathgradeConfig({
|
|
204
|
+
const config = await resolvePathgradeConfig({
|
|
205
|
+
cwd: process.cwd(),
|
|
206
|
+
cli: parsed.attempts === undefined ? undefined : { attempts: parsed.attempts },
|
|
207
|
+
runnerArgs: parsed.runnerArgs,
|
|
208
|
+
});
|
|
209
|
+
if (!parsed.quiet && config.attempts > 1) {
|
|
210
|
+
console.error(`pathgrade: ${config.attempts} sequential attempts per selected case`);
|
|
211
|
+
}
|
|
205
212
|
const runner = await loadRunnerInvocationAdapter({
|
|
206
213
|
adapterName: parsed.adapterName ?? config.runner.adapter,
|
|
207
214
|
cwd: process.cwd(),
|
|
@@ -210,7 +217,7 @@ export async function runPathgradeCli(options = {}) {
|
|
|
210
217
|
process.exitCode = await runner.run({
|
|
211
218
|
cwd: process.cwd(),
|
|
212
219
|
runnerArgs: [...config.runner.args, ...parsed.runnerArgs],
|
|
213
|
-
env,
|
|
220
|
+
env: { ...env, PATHGRADE_ATTEMPT_COUNT: String(config.attempts) },
|
|
214
221
|
});
|
|
215
222
|
}
|
|
216
223
|
catch (err) {
|
|
@@ -233,6 +240,7 @@ function printHelp(cliName) {
|
|
|
233
240
|
[--since=<ref>] Override base ref (implies git mode)
|
|
234
241
|
[--changed-files=<path>] Use an explicit newline-delimited file list
|
|
235
242
|
[--adapter=<name|path>] Select built-in or third-party runner adapter
|
|
243
|
+
[--attempts=N] Run every selected case N times sequentially
|
|
236
244
|
[--diagnostics] Print full diagnostics for passing evals too
|
|
237
245
|
[--quiet] Suppress the run-start summary
|
|
238
246
|
[--verbose|-v] Stream live per-turn events to stderr during the run
|
package/dist/reporters/cli.js
CHANGED
|
@@ -18,8 +18,8 @@ export async function runCliPreview(resultsDir, opts) {
|
|
|
18
18
|
}
|
|
19
19
|
console.log(`\n${fmt.bold('pathgrade preview')} ${fmt.dim(`${entries.length} reports from ${resolved}`)}\n`);
|
|
20
20
|
for (const { file, ...report } of entries) {
|
|
21
|
-
const
|
|
22
|
-
const isPass =
|
|
21
|
+
const meanReward = report.mean_reward ?? report.pass_rate ?? 0;
|
|
22
|
+
const isPass = report.status === undefined ? meanReward >= 0.5 : report.status === 'pass';
|
|
23
23
|
const trials = report.trials || [];
|
|
24
24
|
const avgDur = trials.reduce((s, t) => s + (t.duration_ms || 0), 0) / (trials.length || 1);
|
|
25
25
|
const totalTokens = trials.reduce((s, t) => s + (t.input_tokens || 0) + (t.output_tokens || 0) + (t.conversation_input_tokens || 0) + (t.conversation_output_tokens || 0), 0);
|
|
@@ -33,9 +33,9 @@ export async function runCliPreview(resultsDir, opts) {
|
|
|
33
33
|
console.log();
|
|
34
34
|
// ── Summary metrics
|
|
35
35
|
const metrics = [
|
|
36
|
-
['
|
|
37
|
-
['
|
|
38
|
-
['pass
|
|
36
|
+
['Mean Reward', `${(meanReward * 100).toFixed(1)}%`],
|
|
37
|
+
['Success Rate', report.success_rate != null ? `${(report.success_rate * 100).toFixed(1)}%` : '—'],
|
|
38
|
+
['pass@k', formatPassAtK(report.pass_at_k, report.pass_at_k_unavailable_reason)],
|
|
39
39
|
['Avg Duration', `${(avgDur / 1000).toFixed(1)}s`],
|
|
40
40
|
['Total Tokens', `~${totalTokens}`],
|
|
41
41
|
['Skills', report.skills_used?.join(', ') || 'none'],
|
|
@@ -47,7 +47,7 @@ export async function runCliPreview(resultsDir, opts) {
|
|
|
47
47
|
// ── Trials
|
|
48
48
|
for (const trial of trials) {
|
|
49
49
|
const evaluated = trial.reward !== undefined;
|
|
50
|
-
const tp = evaluated && trial.reward >= 0.5;
|
|
50
|
+
const tp = evaluated && (trial.runner_outcome === undefined ? trial.reward >= 0.5 : trial.runner_outcome === 'passed' && trial.reward === 1);
|
|
51
51
|
const trialStatus = !evaluated ? fmt.dim('N/A') : tp ? fmt.pass('PASS') : fmt.fail('FAIL');
|
|
52
52
|
const reward = fmt.bold(evaluated ? trial.reward.toFixed(2) : 'n/a');
|
|
53
53
|
const dur = `${((trial.duration_ms || 0) / 1000).toFixed(1)}s`;
|
|
@@ -120,6 +120,13 @@ export async function runCliPreview(resultsDir, opts) {
|
|
|
120
120
|
console.log();
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
function formatPassAtK(value, reason) {
|
|
124
|
+
if (typeof value === 'number')
|
|
125
|
+
return `${(value * 100).toFixed(1)}% (legacy v1)`;
|
|
126
|
+
if (value)
|
|
127
|
+
return Object.entries(value).map(([k, metric]) => `@${k} ${(metric * 100).toFixed(1)}%`).join(', ');
|
|
128
|
+
return reason ? `— (${reason.replaceAll('_', ' ')})` : '—';
|
|
129
|
+
}
|
|
123
130
|
function formatScorerStatus(status) {
|
|
124
131
|
switch (status) {
|
|
125
132
|
case 'error':
|
|
@@ -37,6 +37,13 @@ export function commentMarker(commentId) {
|
|
|
37
37
|
function pct(n) {
|
|
38
38
|
return `${(n * 100).toFixed(1)}%`;
|
|
39
39
|
}
|
|
40
|
+
function formatPassAtK(value, reason) {
|
|
41
|
+
if (typeof value === 'number')
|
|
42
|
+
return `${pct(value)} (legacy v1)`;
|
|
43
|
+
if (value)
|
|
44
|
+
return Object.entries(value).map(([k, metric]) => `pass@${k} ${pct(metric)}`).join(', ');
|
|
45
|
+
return reason ? `Unavailable (${reason.replaceAll('_', ' ')})` : 'Unavailable';
|
|
46
|
+
}
|
|
40
47
|
function durationSeconds(ms) {
|
|
41
48
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
42
49
|
}
|
|
@@ -55,10 +62,7 @@ function escapeTableCell(value) {
|
|
|
55
62
|
* posted as a GitHub issue comment body.
|
|
56
63
|
*/
|
|
57
64
|
export function formatReportMarkdown(report, opts) {
|
|
58
|
-
const
|
|
59
|
-
const p = report.overall_pass_rate;
|
|
60
|
-
const overallPassAtK = totalTrials > 0 ? 1 - Math.pow(1 - p, totalTrials) : 0;
|
|
61
|
-
const overallPassPowK = totalTrials > 0 ? Math.pow(p, totalTrials) : 0;
|
|
65
|
+
const meanReward = report.overall_mean_reward ?? report.overall_pass_rate;
|
|
62
66
|
const icon = report.status === 'pass' ? '✅' : '❌';
|
|
63
67
|
const lines = [];
|
|
64
68
|
lines.push(commentMarker(opts.commentId));
|
|
@@ -67,9 +71,8 @@ export function formatReportMarkdown(report, opts) {
|
|
|
67
71
|
lines.push('');
|
|
68
72
|
lines.push(`**Last run:** \`${report.timestamp}\``);
|
|
69
73
|
lines.push('');
|
|
70
|
-
lines.push(`**
|
|
71
|
-
|
|
72
|
-
`**pass^${totalTrials}:** ${pct(overallPassPowK)}`);
|
|
74
|
+
lines.push(`**Mean reward:** ${pct(meanReward)}` +
|
|
75
|
+
(report.attempts_requested ? ` | **Attempts:** ${report.attempts_completed ?? 0}/${report.attempts_requested}` : ''));
|
|
73
76
|
if (opts.detailsUrl) {
|
|
74
77
|
lines.push('');
|
|
75
78
|
lines.push(`**Details:** [Open the full report](${formatLinkDestination(opts.detailsUrl)})`);
|
|
@@ -83,12 +86,12 @@ export function formatReportMarkdown(report, opts) {
|
|
|
83
86
|
lines.push(`Threshold: ${pct(report.threshold)} — ${report.status.toUpperCase()}`);
|
|
84
87
|
}
|
|
85
88
|
lines.push('');
|
|
86
|
-
lines.push('| Group |
|
|
89
|
+
lines.push('| Group | Mean reward | Success rate | pass@k | Skills | Avg duration |');
|
|
87
90
|
lines.push('|---|---|---|---|---|---|');
|
|
88
91
|
for (const group of report.groups) {
|
|
89
92
|
const skills = group.skills_used.length > 0 ? group.skills_used.join(', ') : '—';
|
|
90
93
|
const avg = computeAvgDuration(group.trials);
|
|
91
|
-
lines.push(`| ${escapeTableCell(group.task)} | ${pct(group.pass_rate)} | ${pct(group.
|
|
94
|
+
lines.push(`| ${escapeTableCell(group.task)} | ${pct(group.mean_reward ?? group.pass_rate ?? 0)} | ${group.success_rate === undefined ? '—' : pct(group.success_rate)} | ${escapeTableCell(formatPassAtK(group.pass_at_k, group.pass_at_k_unavailable_reason))} | ${escapeTableCell(skills)} | ${durationSeconds(avg)} |`);
|
|
92
95
|
}
|
|
93
96
|
if (report.selection) {
|
|
94
97
|
lines.push('');
|
package/dist/reporters/loader.js
CHANGED
|
@@ -7,7 +7,13 @@ async function hydrateTraces(report, traceFile, resolved) {
|
|
|
7
7
|
const traceTrials = await fs.readJSON(tracePath);
|
|
8
8
|
if (!Array.isArray(traceTrials) || !Array.isArray(report.trials))
|
|
9
9
|
return;
|
|
10
|
-
|
|
10
|
+
const tracesByAttemptId = new Map(traceTrials
|
|
11
|
+
.filter((trial) => typeof trial?.attempt_id === 'string')
|
|
12
|
+
.map((trial) => [trial.attempt_id, trial]));
|
|
13
|
+
report.trials = report.trials.map((trial, index) => ({
|
|
14
|
+
...trial,
|
|
15
|
+
...(trial.attempt_id ? tracesByAttemptId.get(trial.attempt_id) : traceTrials[index]),
|
|
16
|
+
}));
|
|
11
17
|
}
|
|
12
18
|
export async function loadReports(resultsDir, opts) {
|
|
13
19
|
const resolved = path.resolve(resultsDir);
|
|
@@ -25,8 +31,15 @@ export async function loadReports(resultsDir, opts) {
|
|
|
25
31
|
results.push({ file, ...raw });
|
|
26
32
|
continue;
|
|
27
33
|
}
|
|
34
|
+
if (raw.version !== 1 && raw.version !== 2)
|
|
35
|
+
continue;
|
|
28
36
|
for (const group of raw.groups) {
|
|
29
|
-
const report = {
|
|
37
|
+
const report = {
|
|
38
|
+
file,
|
|
39
|
+
timestamp: raw.timestamp,
|
|
40
|
+
...group,
|
|
41
|
+
status: groupStatus(raw, group),
|
|
42
|
+
};
|
|
30
43
|
if (!opts?.skipTraces && group.trace_file) {
|
|
31
44
|
await hydrateTraces(report, group.trace_file, resolved);
|
|
32
45
|
}
|
|
@@ -37,3 +50,15 @@ export async function loadReports(resultsDir, opts) {
|
|
|
37
50
|
}
|
|
38
51
|
return results;
|
|
39
52
|
}
|
|
53
|
+
function groupStatus(raw, group) {
|
|
54
|
+
if (group.status === 'pass' || group.status === 'fail')
|
|
55
|
+
return group.status;
|
|
56
|
+
const meanReward = group.mean_reward ?? group.pass_rate ?? 0;
|
|
57
|
+
if (typeof raw.threshold === 'number')
|
|
58
|
+
return meanReward >= raw.threshold ? 'pass' : 'fail';
|
|
59
|
+
const outcomes = Array.isArray(group.trials) ? group.trials.map((trial) => trial.runner_outcome) : [];
|
|
60
|
+
if (outcomes.length > 0 && outcomes.every((outcome) => (outcome === 'passed' || outcome === 'failed' || outcome === 'not-run'))) {
|
|
61
|
+
return outcomes.every((outcome) => outcome === 'passed') ? 'pass' : 'fail';
|
|
62
|
+
}
|
|
63
|
+
return meanReward >= 0.5 ? 'pass' : 'fail';
|
|
64
|
+
}
|
|
@@ -3,13 +3,21 @@ import { formatDiagnostics, formatDiagnosticsSummary } from './diagnostics.js';
|
|
|
3
3
|
export function printReportSummary(groups, opts = {}) {
|
|
4
4
|
console.log(`\n${fmt.bold('── pathgrade summary ')}${fmt.dim('─'.repeat(40))}\n`);
|
|
5
5
|
for (const group of groups) {
|
|
6
|
-
const
|
|
6
|
+
const rewardColor = group.mean_reward >= 0.5 ? fmt.green : fmt.red;
|
|
7
7
|
console.log(` ${fmt.bold(group.task)}`);
|
|
8
|
-
console.log(` ${fmt.dim('
|
|
9
|
-
console.log(` ${fmt.dim(
|
|
10
|
-
|
|
8
|
+
console.log(` ${fmt.dim('mean reward'.padEnd(12))} ${rewardColor((group.mean_reward * 100).toFixed(1) + '%')}`);
|
|
9
|
+
console.log(` ${fmt.dim('success'.padEnd(12))} ${group.success_rate === undefined ? '—' : `${(group.success_rate * 100).toFixed(1)}%`}`);
|
|
10
|
+
if (group.pass_at_k) {
|
|
11
|
+
for (const [k, value] of Object.entries(group.pass_at_k)) {
|
|
12
|
+
console.log(` ${fmt.dim(`pass@${k}`.padEnd(12))} ${(value * 100).toFixed(1)}%`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
const reason = group.pass_at_k_unavailable_reason?.replaceAll('_', ' ') ?? 'repeated binary attempts required';
|
|
17
|
+
console.log(` ${fmt.dim('pass@k'.padEnd(12))} — (${reason})`);
|
|
18
|
+
}
|
|
11
19
|
console.log(` ${fmt.dim('avg time'.padEnd(12))} ${(group.average_duration_ms / 1000).toFixed(1)}s`);
|
|
12
|
-
console.log(` ${fmt.dim('
|
|
20
|
+
console.log(` ${fmt.dim('attempts'.padEnd(12))} ${group.trial_count}`);
|
|
13
21
|
console.log();
|
|
14
22
|
for (const diagnostic of group.diagnostics) {
|
|
15
23
|
const shouldPrintFull = opts.forceVerbose
|
package/dist/reporting/core.d.ts
CHANGED