@wix/pathgrade 1.0.29 → 1.0.31
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/adapters/jest/results.d.ts +1 -0
- package/dist/adapters/jest/results.js +6 -0
- package/dist/adapters/jest/runner-adapter.js +3 -0
- package/dist/adapters/node-test/index.js +2 -0
- package/dist/adapters/node-test/runner-adapter.js +6 -5
- package/dist/reporting/comparison-contract.d.ts +10 -0
- package/dist/reporting/comparison-contract.js +127 -0
- package/dist/reporting/core.js +14 -1
- package/dist/reporting/report-parser.d.ts +2 -0
- package/dist/reporting/report-parser.js +112 -0
- package/dist/reporting/source-metadata.d.ts +5 -0
- package/dist/reporting/source-metadata.js +20 -0
- package/dist/reporting/types.d.ts +4 -0
- package/dist/runners/model-builders.d.ts +3 -1
- package/dist/runners/model-builders.js +19 -7
- package/dist/runners/model.d.ts +4 -0
- package/dist/runners/report-projection.js +15 -1
- package/dist/runners/vitest-adapter.js +9 -3
- package/dist/sdk/evaluate.js +5 -0
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +1 -0
- package/dist/sdk/lifecycle.js +1 -0
- package/dist/sdk/scorer-utils.js +13 -3
- package/dist/sdk/types.d.ts +3 -0
- package/dist/types.d.ts +14 -0
- package/dist/utils/llm-providers/anthropic.js +5 -2
- package/dist/utils/llm.js +8 -2
- package/package.json +2 -2
|
@@ -23,6 +23,7 @@ export declare function normalizeJestRunResults(input: {
|
|
|
23
23
|
run: AdapterRunHandle;
|
|
24
24
|
results: JestAggregatedResult;
|
|
25
25
|
metadataByCaseId?: Map<string, PathgradeTestMeta[]>;
|
|
26
|
+
cwd?: string;
|
|
26
27
|
}): NormalizedRunSnapshot;
|
|
27
28
|
export declare function jestCaseId(input: {
|
|
28
29
|
filePath: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveSourceMetadata } from '../../reporting/source-metadata.js';
|
|
1
2
|
export function normalizeJestRunResults(input) {
|
|
2
3
|
const runId = `${input.run.adapterName}:run`;
|
|
3
4
|
const files = input.results.testResults ?? [];
|
|
@@ -5,6 +6,7 @@ export function normalizeJestRunResults(input) {
|
|
|
5
6
|
id: unitId(index),
|
|
6
7
|
runId,
|
|
7
8
|
displayName: fileResult.testFilePath,
|
|
9
|
+
...resolveSourceMetadata(fileResult.testFilePath, input.cwd ?? process.cwd()),
|
|
8
10
|
groupingHints: [{
|
|
9
11
|
kind: 'source',
|
|
10
12
|
key: fileResult.testFilePath,
|
|
@@ -68,6 +70,10 @@ function runCase(input) {
|
|
|
68
70
|
: {}),
|
|
69
71
|
...(entry.recordedAt ? { recordedAt: entry.recordedAt } : {}),
|
|
70
72
|
...(entry.agent ? { agent: entry.agent } : {}),
|
|
73
|
+
...(entry.evaluationDefinitionKey
|
|
74
|
+
? { evaluationDefinitionKey: entry.evaluationDefinitionKey }
|
|
75
|
+
: {}),
|
|
76
|
+
...(entry.scorerRevision ? { scorerRevision: entry.scorerRevision } : {}),
|
|
71
77
|
}));
|
|
72
78
|
const failureMessage = input.assertion.failureMessages?.find(message => message.trim().length > 0);
|
|
73
79
|
return {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { normalizeJestRunResults } from './results.js';
|
|
2
2
|
import { discoverPathgradeEvalFiles, } from '@wix/pathgrade/adapter-kit';
|
|
3
3
|
export function createJestAdapter(options = {}) {
|
|
4
|
+
let discoveryCwd = process.cwd();
|
|
4
5
|
return {
|
|
5
6
|
name: 'jest',
|
|
6
7
|
async discover(input) {
|
|
8
|
+
discoveryCwd = input.cwd;
|
|
7
9
|
const include = input.include ?? ['**/*.eval.ts'];
|
|
8
10
|
const exclude = input.exclude ?? [];
|
|
9
11
|
const files = discoverPathgradeEvalFiles({
|
|
@@ -34,6 +36,7 @@ export function createJestAdapter(options = {}) {
|
|
|
34
36
|
run,
|
|
35
37
|
results: native.results ?? { testResults: [] },
|
|
36
38
|
metadataByCaseId: native.metadataByCaseId,
|
|
39
|
+
cwd: discoveryCwd,
|
|
37
40
|
});
|
|
38
41
|
},
|
|
39
42
|
};
|
|
@@ -85,6 +85,8 @@ function toReportEvaluations(evaluations) {
|
|
|
85
85
|
scoringDurationMs: entry.scoringDurationMs,
|
|
86
86
|
recordedAt: entry.recordedAt,
|
|
87
87
|
agent: entry.agent,
|
|
88
|
+
evaluationDefinitionKey: entry.evaluationDefinitionKey,
|
|
89
|
+
scorerRevision: entry.scorerRevision,
|
|
88
90
|
}));
|
|
89
91
|
}
|
|
90
92
|
function writeCase(testCase) {
|
|
@@ -37,7 +37,7 @@ export function createNodeTestAdapter() {
|
|
|
37
37
|
adapterName: this.name,
|
|
38
38
|
status: 'completed',
|
|
39
39
|
exitCode: 0,
|
|
40
|
-
native: { resultsPath, resultsDir },
|
|
40
|
+
native: { resultsPath, resultsDir, cwd },
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
const exitCode = await spawnNodeTest({
|
|
@@ -54,7 +54,7 @@ export function createNodeTestAdapter() {
|
|
|
54
54
|
adapterName: this.name,
|
|
55
55
|
status: input.signal?.aborted ? 'cancelled' : (exitCode === 0 ? 'completed' : 'failed'),
|
|
56
56
|
exitCode,
|
|
57
|
-
native: { resultsPath, resultsDir },
|
|
57
|
+
native: { resultsPath, resultsDir, cwd },
|
|
58
58
|
};
|
|
59
59
|
},
|
|
60
60
|
async collectNormalizedRunSnapshot(run) {
|
|
@@ -71,7 +71,7 @@ export function createNodeTestAdapter() {
|
|
|
71
71
|
return buildNormalizedRunSnapshotFromReportGroups(run, Array.from(groupMap.entries()).map(([groupName, groupedCases]) => ({
|
|
72
72
|
groupName,
|
|
73
73
|
cases: groupedCases,
|
|
74
|
-
})));
|
|
74
|
+
})), { cwd: native.cwd });
|
|
75
75
|
}
|
|
76
76
|
finally {
|
|
77
77
|
if (native.resultsDir)
|
|
@@ -103,10 +103,11 @@ function readNodeTestRunNative(run) {
|
|
|
103
103
|
if (typeof run.native === 'object'
|
|
104
104
|
&& run.native !== null
|
|
105
105
|
&& typeof run.native.resultsPath === 'string'
|
|
106
|
-
&& typeof run.native.resultsDir === 'string'
|
|
106
|
+
&& typeof run.native.resultsDir === 'string'
|
|
107
|
+
&& typeof run.native.cwd === 'string') {
|
|
107
108
|
return run.native;
|
|
108
109
|
}
|
|
109
|
-
return { resultsPath: '', resultsDir: '' };
|
|
110
|
+
return { resultsPath: '', resultsDir: '', cwd: process.cwd() };
|
|
110
111
|
}
|
|
111
112
|
async function readCases(resultsPath) {
|
|
112
113
|
if (!resultsPath || !(await fs.pathExists(resultsPath)))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComparisonContract, EvalReport } from '../types.js';
|
|
2
|
+
import type { Scorer } from '../sdk/types.js';
|
|
3
|
+
import type { ReportGroupInput } from './types.js';
|
|
4
|
+
export declare function buildComparisonContract(input: {
|
|
5
|
+
group: ReportGroupInput;
|
|
6
|
+
report: EvalReport;
|
|
7
|
+
attemptsRequested: number;
|
|
8
|
+
attemptsCompleted: number;
|
|
9
|
+
}): ComparisonContract;
|
|
10
|
+
export declare function createScorerRevision(scorers: readonly Scorer[]): string | undefined;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonicalizeJson } from '../core/canonical-json.js';
|
|
3
|
+
export function buildComparisonContract(input) {
|
|
4
|
+
const unavailable = new Set();
|
|
5
|
+
const evaluations = input.group.cases.flatMap(testCase => {
|
|
6
|
+
const caseIdentity = testCase.repeatKey ?? testCase.caseId ?? testCase.name;
|
|
7
|
+
return (testCase.attempts?.flatMap(attempt => attempt.evaluations ?? [])
|
|
8
|
+
?? testCase.evaluations
|
|
9
|
+
?? [])
|
|
10
|
+
.filter(evaluation => evaluation.resultKind !== 'synthetic_no_evaluation')
|
|
11
|
+
.map((evaluation, index) => ({
|
|
12
|
+
case: caseIdentity,
|
|
13
|
+
definition: evaluation.evaluationDefinitionKey ?? `position:${index}`,
|
|
14
|
+
scorer: evaluation.scorerRevision,
|
|
15
|
+
runtime: runtimeIdentity(evaluation.agent ?? evaluation.trial?.agent),
|
|
16
|
+
}));
|
|
17
|
+
});
|
|
18
|
+
const definitionRevision = input.group.sourceRevision
|
|
19
|
+
? revision('definition', {
|
|
20
|
+
source: input.group.sourceRevision,
|
|
21
|
+
evaluations: uniqueSorted(evaluations.map(evaluation => ({
|
|
22
|
+
case: evaluation.case, definition: evaluation.definition,
|
|
23
|
+
}))),
|
|
24
|
+
})
|
|
25
|
+
: undefined;
|
|
26
|
+
if (!definitionRevision)
|
|
27
|
+
unavailable.add('definition_metadata_missing');
|
|
28
|
+
const scorerRevision = evaluations.length > 0 && evaluations.every(evaluation => evaluation.scorer)
|
|
29
|
+
? revision('scorers', uniqueSorted(evaluations.map(evaluation => ({
|
|
30
|
+
case: evaluation.case,
|
|
31
|
+
definition: evaluation.definition,
|
|
32
|
+
scorer: evaluation.scorer,
|
|
33
|
+
}))))
|
|
34
|
+
: undefined;
|
|
35
|
+
if (!scorerRevision)
|
|
36
|
+
unavailable.add('scorer_metadata_missing');
|
|
37
|
+
const runtimeComplete = evaluations.length > 0
|
|
38
|
+
&& evaluations.every(evaluation => Object.values(evaluation.runtime)
|
|
39
|
+
.every(value => value !== undefined));
|
|
40
|
+
const runtimeRevision = runtimeComplete
|
|
41
|
+
? revision('runtime', uniqueSorted(evaluations.map(evaluation => ({
|
|
42
|
+
case: evaluation.case,
|
|
43
|
+
definition: evaluation.definition,
|
|
44
|
+
runtime: evaluation.runtime,
|
|
45
|
+
}))))
|
|
46
|
+
: undefined;
|
|
47
|
+
if (!runtimeRevision)
|
|
48
|
+
unavailable.add('runtime_metadata_missing');
|
|
49
|
+
const reportableCases = input.group.cases.filter(testCase => testCase.reportable !== false
|
|
50
|
+
&& testCase.state !== 'skipped' && testCase.state !== 'pending');
|
|
51
|
+
const samplingComplete = input.attemptsCompleted === input.attemptsRequested
|
|
52
|
+
&& reportableCases.every(testCase => (testCase.attempts?.length ?? (testCase.evaluations === undefined ? 0 : 1)) === input.attemptsRequested)
|
|
53
|
+
&& reportableCases.every(testCase => testCase.attempts
|
|
54
|
+
? testCase.attempts.every(attempt => hasTerminalEvaluation(attempt.evaluations))
|
|
55
|
+
: hasTerminalEvaluation(testCase.evaluations));
|
|
56
|
+
const samplingRevision = samplingComplete
|
|
57
|
+
? revision('sampling', {
|
|
58
|
+
attempts: input.attemptsRequested,
|
|
59
|
+
cases: reportableCases.map(testCase => testCase.repeatKey ?? testCase.caseId ?? testCase.name).toSorted(),
|
|
60
|
+
})
|
|
61
|
+
: undefined;
|
|
62
|
+
if (!samplingRevision)
|
|
63
|
+
unavailable.add('sampling_incomplete');
|
|
64
|
+
return {
|
|
65
|
+
version: 1,
|
|
66
|
+
...(definitionRevision ? { definition_revision: definitionRevision } : {}),
|
|
67
|
+
...(scorerRevision ? { scorer_revision: scorerRevision } : {}),
|
|
68
|
+
...(runtimeRevision ? { runtime_revision: runtimeRevision } : {}),
|
|
69
|
+
...(samplingRevision ? { sampling_revision: samplingRevision } : {}),
|
|
70
|
+
report_schema_revision: 'pathgrade-results-v2',
|
|
71
|
+
...(unavailable.size > 0 ? { unavailable_reasons: [...unavailable].sort() } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function hasTerminalEvaluation(evaluations) {
|
|
75
|
+
return evaluations?.some(evaluation => evaluation.resultKind !== 'synthetic_no_evaluation') === true;
|
|
76
|
+
}
|
|
77
|
+
export function createScorerRevision(scorers) {
|
|
78
|
+
try {
|
|
79
|
+
return revision('scorer-declaration', scorers
|
|
80
|
+
.map(scorer => canonicalizeJson(scorerDeclaration(scorer))).toSorted());
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function runtimeIdentity(agent) {
|
|
87
|
+
return {
|
|
88
|
+
name: agent?.name,
|
|
89
|
+
model: agent?.resolvedModel,
|
|
90
|
+
transport: agent?.transport,
|
|
91
|
+
interaction: agent?.interactionMode,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function scorerDeclaration(scorer) {
|
|
95
|
+
const common = { type: scorer.type, name: scorer.name.normalize('NFC'), weight: scorer.weight };
|
|
96
|
+
if (scorer.type === 'check' || scorer.type === 'score') {
|
|
97
|
+
return { ...common, function: scorer.fn.toString() };
|
|
98
|
+
}
|
|
99
|
+
if (scorer.type === 'tool_usage') {
|
|
100
|
+
return {
|
|
101
|
+
...common,
|
|
102
|
+
expectations: scorer.expectations
|
|
103
|
+
.toSorted((left, right) => canonicalizeJson(left).localeCompare(canonicalizeJson(right))),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
...common,
|
|
108
|
+
rubric: scorer.rubric,
|
|
109
|
+
model: scorer.model ?? null,
|
|
110
|
+
retry: scorer.retry ?? null,
|
|
111
|
+
includeToolEvents: scorer.includeToolEvents ?? null,
|
|
112
|
+
input: typeof scorer.input === 'function' ? scorer.input.toString() : scorer.input ?? null,
|
|
113
|
+
tools: scorer.tools?.toSorted() ?? null,
|
|
114
|
+
maxRounds: scorer.maxRounds ?? null,
|
|
115
|
+
cacheControl: scorer.cacheControl ?? null,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function revision(namespace, value) {
|
|
119
|
+
return `sha256:${createHash('sha256')
|
|
120
|
+
.update(canonicalizeJson(['pathgrade-comparison', 1, namespace, value]))
|
|
121
|
+
.digest('hex')}`;
|
|
122
|
+
}
|
|
123
|
+
function uniqueSorted(values) {
|
|
124
|
+
return [...new Map(values.map(value => [canonicalizeJson(value), value])).entries()]
|
|
125
|
+
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
126
|
+
.map(([, value]) => value);
|
|
127
|
+
}
|
package/dist/reporting/core.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildDiagnosticsReport } from '../sdk/diagnostics.js';
|
|
2
2
|
import { extractSkillsFromLog } from '../tool-events.js';
|
|
3
|
+
import { buildComparisonContract } from './comparison-contract.js';
|
|
3
4
|
export function buildPathgradeReport(input) {
|
|
4
5
|
const warnings = [];
|
|
5
6
|
const consolidatedGroups = [];
|
|
@@ -8,6 +9,7 @@ export function buildPathgradeReport(input) {
|
|
|
8
9
|
const attemptsRequested = input.attemptsRequested ?? 1;
|
|
9
10
|
const attemptsCompleted = input.attemptsCompleted ?? attemptsRequested;
|
|
10
11
|
const builtGroups = input.groups.map(group => ({
|
|
12
|
+
input: group,
|
|
11
13
|
groupName: group.groupName,
|
|
12
14
|
cases: group.cases.map(toBuiltCase),
|
|
13
15
|
}));
|
|
@@ -32,7 +34,18 @@ export function buildPathgradeReport(input) {
|
|
|
32
34
|
return rest;
|
|
33
35
|
});
|
|
34
36
|
const { trials: _trials, ...rest } = report;
|
|
35
|
-
consolidatedGroups.push({
|
|
37
|
+
consolidatedGroups.push({
|
|
38
|
+
...rest,
|
|
39
|
+
trials: strippedTrials,
|
|
40
|
+
trace_file: traceFile,
|
|
41
|
+
...(group.input.sourceFile ? { source_file: group.input.sourceFile } : {}),
|
|
42
|
+
comparison_contract: buildComparisonContract({
|
|
43
|
+
group: group.input,
|
|
44
|
+
report,
|
|
45
|
+
attemptsRequested,
|
|
46
|
+
attemptsCompleted,
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
36
49
|
}
|
|
37
50
|
const scores = reportableGroups.flatMap(group => group.cases.flatMap(testCase => (testCase.attempts
|
|
38
51
|
.filter(attempt => attempt.resultKind !== 'synthetic_no_evaluation')
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export function parsePathgradeReport(value) {
|
|
2
|
+
if (!isRecord(value)
|
|
3
|
+
|| (value.version !== 1 && value.version !== 2)
|
|
4
|
+
|| typeof value.timestamp !== 'string'
|
|
5
|
+
|| typeof value.overall_pass_rate !== 'number'
|
|
6
|
+
|| (value.version === 2 && (typeof value.overall_mean_reward !== 'number' || !validAttemptCounts(value)))
|
|
7
|
+
|| !optionalNumber(value.threshold)
|
|
8
|
+
|| (value.status !== 'pass' && value.status !== 'fail')
|
|
9
|
+
|| (value.run_kind !== undefined && value.run_kind !== 'evaluation' && value.run_kind !== 'no-affected')
|
|
10
|
+
|| !Array.isArray(value.groups)
|
|
11
|
+
|| !value.groups.every(group => isReportGroup(group, value.version))
|
|
12
|
+
|| !isSelection(value.selection)) {
|
|
13
|
+
throw new Error('PathGrade results.json is missing or has an unsupported schema');
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
function isReportGroup(value, version) {
|
|
18
|
+
return isRecord(value)
|
|
19
|
+
&& typeof value.task === 'string'
|
|
20
|
+
&& (version === 1 ? legacyMetrics(value) : schemaV2Metrics(value))
|
|
21
|
+
&& optionalString(value.source_file)
|
|
22
|
+
&& typeof value.trace_file === 'string'
|
|
23
|
+
&& Array.isArray(value.skills_used)
|
|
24
|
+
&& value.skills_used.every(item => typeof item === 'string')
|
|
25
|
+
&& Array.isArray(value.trials)
|
|
26
|
+
&& value.trials.every(isTrial)
|
|
27
|
+
&& (value.comparison_contract === undefined || isComparisonContract(value.comparison_contract));
|
|
28
|
+
}
|
|
29
|
+
function isTrial(value) {
|
|
30
|
+
return isRecord(value)
|
|
31
|
+
&& typeof value.trial_id === 'number'
|
|
32
|
+
&& typeof value.duration_ms === 'number'
|
|
33
|
+
&& typeof value.input_tokens === 'number'
|
|
34
|
+
&& typeof value.output_tokens === 'number'
|
|
35
|
+
&& optionalNumber(value.reward)
|
|
36
|
+
&& optionalNumber(value.conversation_input_tokens)
|
|
37
|
+
&& optionalNumber(value.conversation_output_tokens)
|
|
38
|
+
&& optionalNumber(value.conversation_cost_usd)
|
|
39
|
+
&& Array.isArray(value.scorer_results)
|
|
40
|
+
&& value.scorer_results.every(scorer => isRecord(scorer)
|
|
41
|
+
&& typeof scorer.scorer_type === 'string'
|
|
42
|
+
&& typeof scorer.score === 'number'
|
|
43
|
+
&& typeof scorer.weight === 'number');
|
|
44
|
+
}
|
|
45
|
+
function legacyMetrics(value) {
|
|
46
|
+
return typeof value.pass_rate === 'number'
|
|
47
|
+
&& typeof value.pass_at_k === 'number'
|
|
48
|
+
&& typeof value.pass_pow_k === 'number';
|
|
49
|
+
}
|
|
50
|
+
function schemaV2Metrics(value) {
|
|
51
|
+
return (value.status === 'pass' || value.status === 'fail')
|
|
52
|
+
&& typeof value.mean_reward === 'number'
|
|
53
|
+
&& optionalNumber(value.success_rate)
|
|
54
|
+
&& (value.pass_at_k === undefined || (isRecord(value.pass_at_k)
|
|
55
|
+
&& Object.values(value.pass_at_k).every(item => typeof item === 'number')))
|
|
56
|
+
&& (value.pass_at_k_method === undefined || value.pass_at_k_method === 'finite_sample_unbiased')
|
|
57
|
+
&& (value.pass_at_k_unavailable_reason === undefined
|
|
58
|
+
|| value.pass_at_k_unavailable_reason === 'non_binary_reward'
|
|
59
|
+
|| value.pass_at_k_unavailable_reason === 'incomplete_attempts'
|
|
60
|
+
|| value.pass_at_k_unavailable_reason === 'case_identity_mismatch')
|
|
61
|
+
&& optionalNonNegativeInteger(value.eligible_case_count)
|
|
62
|
+
&& optionalPositiveInteger(value.attempts_per_case);
|
|
63
|
+
}
|
|
64
|
+
function validAttemptCounts(value) {
|
|
65
|
+
return typeof value.attempts_requested === 'number'
|
|
66
|
+
&& Number.isSafeInteger(value.attempts_requested)
|
|
67
|
+
&& value.attempts_requested >= 1
|
|
68
|
+
&& typeof value.attempts_completed === 'number'
|
|
69
|
+
&& Number.isSafeInteger(value.attempts_completed)
|
|
70
|
+
&& value.attempts_completed >= 0
|
|
71
|
+
&& value.attempts_completed <= value.attempts_requested;
|
|
72
|
+
}
|
|
73
|
+
function isSelection(value) {
|
|
74
|
+
return value === undefined || (isRecord(value)
|
|
75
|
+
&& typeof value.base_ref === 'string'
|
|
76
|
+
&& typeof value.changed_files_count === 'number'
|
|
77
|
+
&& Array.isArray(value.selected)
|
|
78
|
+
&& value.selected.every(item => typeof item === 'string')
|
|
79
|
+
&& Array.isArray(value.skipped)
|
|
80
|
+
&& value.skipped.every(item => isRecord(item)
|
|
81
|
+
&& typeof item.file === 'string'
|
|
82
|
+
&& item.reason === 'no-matching-deps'));
|
|
83
|
+
}
|
|
84
|
+
function isComparisonContract(value) {
|
|
85
|
+
return isRecord(value)
|
|
86
|
+
&& value.version === 1
|
|
87
|
+
&& optionalString(value.definition_revision)
|
|
88
|
+
&& optionalString(value.scorer_revision)
|
|
89
|
+
&& optionalString(value.runtime_revision)
|
|
90
|
+
&& optionalString(value.sampling_revision)
|
|
91
|
+
&& value.report_schema_revision === 'pathgrade-results-v2'
|
|
92
|
+
&& (value.unavailable_reasons === undefined || (Array.isArray(value.unavailable_reasons)
|
|
93
|
+
&& value.unavailable_reasons.every(reason => reason === 'definition_metadata_missing'
|
|
94
|
+
|| reason === 'scorer_metadata_missing'
|
|
95
|
+
|| reason === 'runtime_metadata_missing'
|
|
96
|
+
|| reason === 'sampling_incomplete')));
|
|
97
|
+
}
|
|
98
|
+
function optionalString(value) {
|
|
99
|
+
return value === undefined || typeof value === 'string';
|
|
100
|
+
}
|
|
101
|
+
function optionalNumber(value) {
|
|
102
|
+
return value === undefined || typeof value === 'number';
|
|
103
|
+
}
|
|
104
|
+
function optionalNonNegativeInteger(value) {
|
|
105
|
+
return value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0);
|
|
106
|
+
}
|
|
107
|
+
function optionalPositiveInteger(value) {
|
|
108
|
+
return value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value >= 1);
|
|
109
|
+
}
|
|
110
|
+
function isRecord(value) {
|
|
111
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
112
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
export function resolveSourceMetadata(filePath, cwd) {
|
|
5
|
+
if (!filePath)
|
|
6
|
+
return {};
|
|
7
|
+
const absolute = path.resolve(cwd, filePath);
|
|
8
|
+
const relative = path.relative(cwd, absolute).replaceAll(path.sep, '/');
|
|
9
|
+
if (!relative || relative === '..' || relative.startsWith('../'))
|
|
10
|
+
return {};
|
|
11
|
+
try {
|
|
12
|
+
return {
|
|
13
|
+
sourceFile: relative,
|
|
14
|
+
sourceRevision: `sha256:${createHash('sha256').update(readFileSync(absolute)).digest('hex')}`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return { sourceFile: relative };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -12,6 +12,8 @@ export interface ReportRunInput {
|
|
|
12
12
|
}
|
|
13
13
|
export interface ReportGroupInput {
|
|
14
14
|
groupName: string;
|
|
15
|
+
sourceFile?: string;
|
|
16
|
+
sourceRevision?: string;
|
|
15
17
|
cases: ReportCaseInput[];
|
|
16
18
|
}
|
|
17
19
|
export interface ReportCaseInput {
|
|
@@ -45,6 +47,8 @@ export interface ReportEvaluationInput {
|
|
|
45
47
|
scoringDurationMs?: number;
|
|
46
48
|
recordedAt?: string;
|
|
47
49
|
agent?: AgentExecutionMetadata;
|
|
50
|
+
evaluationDefinitionKey?: string;
|
|
51
|
+
scorerRevision?: string;
|
|
48
52
|
}
|
|
49
53
|
export interface PathgradeReportBuildResult {
|
|
50
54
|
report: PathgradeReport;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ReportGroupInput } from '../reporting/types.js';
|
|
2
2
|
import type { AdapterRunHandle } from './adapter.js';
|
|
3
3
|
import type { NormalizedRunSnapshot } from './model.js';
|
|
4
|
-
export declare function buildNormalizedRunSnapshotFromReportGroups(run: AdapterRunHandle, groups: ReportGroupInput[]
|
|
4
|
+
export declare function buildNormalizedRunSnapshotFromReportGroups(run: AdapterRunHandle, groups: ReportGroupInput[], options?: {
|
|
5
|
+
cwd?: string;
|
|
6
|
+
}): NormalizedRunSnapshot;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { resolveSourceMetadata } from '../reporting/source-metadata.js';
|
|
2
|
+
export function buildNormalizedRunSnapshotFromReportGroups(run, groups, options = {}) {
|
|
3
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2
4
|
return {
|
|
3
5
|
version: 1,
|
|
4
6
|
completeness: 'final',
|
|
@@ -12,12 +14,18 @@ export function buildNormalizedRunSnapshotFromReportGroups(run, groups) {
|
|
|
12
14
|
message: diagnostic.message,
|
|
13
15
|
})) } : {}),
|
|
14
16
|
},
|
|
15
|
-
units: groups.map((group, index) =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
units: groups.map((group, index) => {
|
|
18
|
+
const source = group.sourceFile && group.sourceRevision
|
|
19
|
+
? { sourceFile: group.sourceFile, sourceRevision: group.sourceRevision }
|
|
20
|
+
: resolveSourceMetadata(group.sourceFile ?? group.cases.map(testCase => testCase.filePath ?? testCase.sourceRef).find(Boolean), cwd);
|
|
21
|
+
return {
|
|
22
|
+
id: unitId(group.groupName, index),
|
|
23
|
+
runId: `${run.adapterName}:run`,
|
|
24
|
+
displayName: group.groupName,
|
|
25
|
+
...source,
|
|
26
|
+
groupingHints: [{ kind: 'suite', key: group.groupName, label: group.groupName, order: index }],
|
|
27
|
+
};
|
|
28
|
+
}),
|
|
21
29
|
cases: groups.flatMap((group, groupIndex) => group.cases.map((testCase, caseIndex) => {
|
|
22
30
|
const caseId = testCase.caseId ?? `${unitId(group.groupName, groupIndex)}:case-${caseIndex + 1}`;
|
|
23
31
|
const attemptId = `${caseId}:attempt-1`;
|
|
@@ -33,6 +41,10 @@ export function buildNormalizedRunSnapshotFromReportGroups(run, groups) {
|
|
|
33
41
|
: {}),
|
|
34
42
|
...(evaluation.recordedAt ? { recordedAt: evaluation.recordedAt } : {}),
|
|
35
43
|
...(evaluation.agent ? { agent: evaluation.agent } : {}),
|
|
44
|
+
...(evaluation.evaluationDefinitionKey
|
|
45
|
+
? { evaluationDefinitionKey: evaluation.evaluationDefinitionKey }
|
|
46
|
+
: {}),
|
|
47
|
+
...(evaluation.scorerRevision ? { scorerRevision: evaluation.scorerRevision } : {}),
|
|
36
48
|
}));
|
|
37
49
|
return {
|
|
38
50
|
id: caseId,
|
package/dist/runners/model.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface EvalUnitRecord {
|
|
|
25
25
|
id: string;
|
|
26
26
|
runId: string;
|
|
27
27
|
displayName: string;
|
|
28
|
+
sourceFile?: string;
|
|
29
|
+
sourceRevision?: string;
|
|
28
30
|
diagnostics?: Diagnostic[];
|
|
29
31
|
nativeReferences?: NativeReference[];
|
|
30
32
|
groupingHints?: GroupingHint[];
|
|
@@ -92,6 +94,8 @@ export interface EvaluationRecord {
|
|
|
92
94
|
scoringDurationMs?: number;
|
|
93
95
|
recordedAt?: string;
|
|
94
96
|
agent?: import('../sdk/types.js').AgentExecutionMetadata;
|
|
97
|
+
evaluationDefinitionKey?: string;
|
|
98
|
+
scorerRevision?: string;
|
|
95
99
|
}
|
|
96
100
|
export interface AssertionRecord {
|
|
97
101
|
id: string;
|
|
@@ -16,9 +16,15 @@ export function projectNormalizedRunSnapshotToReportInput(snapshot, options = {}
|
|
|
16
16
|
?? preferredGroupingLabel(unit?.groupingHints)
|
|
17
17
|
?? unit?.displayName;
|
|
18
18
|
const key = groupName ?? snapshot.model.run.adapterName;
|
|
19
|
-
const group = groups.get(key) ?? {
|
|
19
|
+
const group = groups.get(key) ?? {
|
|
20
|
+
groupName: key,
|
|
21
|
+
...(unit?.sourceFile ? { sourceFile: unit.sourceFile } : {}),
|
|
22
|
+
...(unit?.sourceRevision ? { sourceRevision: unit.sourceRevision } : {}),
|
|
23
|
+
cases: [],
|
|
24
|
+
};
|
|
20
25
|
group.cases.push({
|
|
21
26
|
caseId: runCase.id,
|
|
27
|
+
...(runCase.repeatKey ? { repeatKey: runCase.repeatKey } : {}),
|
|
22
28
|
name: runCase.name,
|
|
23
29
|
state: runCase.state,
|
|
24
30
|
...(runCase.state === 'skipped' || runCase.state === 'pending' ? { reportable: repeated || runCase.scoringPolicy.kind !== 'non-scoring' } : {}),
|
|
@@ -53,6 +59,10 @@ function projectedEvaluations(runCase, attemptEvaluations) {
|
|
|
53
59
|
...(evaluation.scoringDurationMs !== undefined ? { scoringDurationMs: evaluation.scoringDurationMs } : {}),
|
|
54
60
|
...(evaluation.recordedAt ? { recordedAt: evaluation.recordedAt } : {}),
|
|
55
61
|
...(evaluation.agent ? { agent: evaluation.agent } : {}),
|
|
62
|
+
...(evaluation.evaluationDefinitionKey
|
|
63
|
+
? { evaluationDefinitionKey: evaluation.evaluationDefinitionKey }
|
|
64
|
+
: {}),
|
|
65
|
+
...(evaluation.scorerRevision ? { scorerRevision: evaluation.scorerRevision } : {}),
|
|
56
66
|
}));
|
|
57
67
|
}
|
|
58
68
|
if (runCase.scoringPolicy.kind === 'score') {
|
|
@@ -70,6 +80,10 @@ function projectedEvaluations(runCase, attemptEvaluations) {
|
|
|
70
80
|
...(evaluation.scoringDurationMs !== undefined ? { scoringDurationMs: evaluation.scoringDurationMs } : {}),
|
|
71
81
|
...(evaluation.recordedAt ? { recordedAt: evaluation.recordedAt } : {}),
|
|
72
82
|
...(evaluation.agent ? { agent: evaluation.agent } : {}),
|
|
83
|
+
...(evaluation.evaluationDefinitionKey
|
|
84
|
+
? { evaluationDefinitionKey: evaluation.evaluationDefinitionKey }
|
|
85
|
+
: {}),
|
|
86
|
+
...(evaluation.scorerRevision ? { scorerRevision: evaluation.scorerRevision } : {}),
|
|
73
87
|
}];
|
|
74
88
|
}
|
|
75
89
|
function preferredGroupingLabel(hints) {
|
|
@@ -2,9 +2,11 @@ import { discoverPathgradeEvalFiles } from '../evals/discovery.js';
|
|
|
2
2
|
import { buildNormalizedRunSnapshotFromReportGroups } from './model-builders.js';
|
|
3
3
|
import { buildDiagnosticsReport } from '../sdk/diagnostics.js';
|
|
4
4
|
export function createVitestAdapter(options = {}) {
|
|
5
|
+
let discoveryCwd = process.cwd();
|
|
5
6
|
return {
|
|
6
7
|
name: 'vitest',
|
|
7
8
|
async discover(input) {
|
|
9
|
+
discoveryCwd = input.cwd;
|
|
8
10
|
const include = input.include ?? ['**/*.eval.ts'];
|
|
9
11
|
const exclude = input.exclude ?? [];
|
|
10
12
|
const files = discoverPathgradeEvalFiles({
|
|
@@ -28,11 +30,12 @@ export function createVitestAdapter(options = {}) {
|
|
|
28
30
|
exitCode: input.signal?.aborted ? 1 : 0,
|
|
29
31
|
native: {
|
|
30
32
|
testModules: options.testModules ?? [],
|
|
33
|
+
cwd: discoveryCwd,
|
|
31
34
|
},
|
|
32
35
|
};
|
|
33
36
|
},
|
|
34
37
|
async collectNormalizedRunSnapshot(run) {
|
|
35
|
-
return buildNormalizedRunSnapshotFromReportGroups(run, collectVitestReportGroups(readVitestRunNative(run).testModules));
|
|
38
|
+
return buildNormalizedRunSnapshotFromReportGroups(run, collectVitestReportGroups(readVitestRunNative(run).testModules), { cwd: readVitestRunNative(run).cwd });
|
|
36
39
|
},
|
|
37
40
|
};
|
|
38
41
|
}
|
|
@@ -56,12 +59,13 @@ export function collectVitestReportGroups(testModules) {
|
|
|
56
59
|
function readVitestRunNative(run) {
|
|
57
60
|
if (isVitestRunNative(run.native))
|
|
58
61
|
return run.native;
|
|
59
|
-
return { testModules: [] };
|
|
62
|
+
return { testModules: [], cwd: process.cwd() };
|
|
60
63
|
}
|
|
61
64
|
function isVitestRunNative(native) {
|
|
62
65
|
return typeof native === 'object'
|
|
63
66
|
&& native !== null
|
|
64
|
-
&& Array.isArray(native.testModules)
|
|
67
|
+
&& Array.isArray(native.testModules)
|
|
68
|
+
&& typeof native.cwd === 'string';
|
|
65
69
|
}
|
|
66
70
|
function getGroupName(testCase) {
|
|
67
71
|
const modulePath = testCase.module.relativeModuleId;
|
|
@@ -94,6 +98,8 @@ function toReportCaseInput(testCase, groupName) {
|
|
|
94
98
|
scoringDurationMs: entry.scoringDurationMs,
|
|
95
99
|
recordedAt: entry.recordedAt,
|
|
96
100
|
agent: entry.agent,
|
|
101
|
+
evaluationDefinitionKey: entry.evaluationDefinitionKey,
|
|
102
|
+
scorerRevision: entry.scorerRevision,
|
|
97
103
|
})),
|
|
98
104
|
diagnostics: normalized.diagnostics,
|
|
99
105
|
};
|
package/dist/sdk/evaluate.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { countShellCommandsFromLog, extractSkillsFromLog, extractToolEventsFromLog } from '../tool-events.js';
|
|
2
|
+
import { createScorerRevision } from '../reporting/comparison-contract.js';
|
|
2
3
|
import { getRuntime } from './eval-runtime.js';
|
|
3
4
|
import { emitEvalResult } from './result-capture.js';
|
|
4
5
|
import { runJudgePipeline } from './judge-pipeline.js';
|
|
@@ -73,6 +74,7 @@ function makeEvaluateAgent() {
|
|
|
73
74
|
: undefined;
|
|
74
75
|
if (isFirstEval)
|
|
75
76
|
conversationAttributed.add(agent);
|
|
77
|
+
const scorerRevision = createScorerRevision(scorers);
|
|
76
78
|
const recordedResult = {
|
|
77
79
|
...evalResult,
|
|
78
80
|
tokenUsage: deltaTokenUsage,
|
|
@@ -82,6 +84,7 @@ function makeEvaluateAgent() {
|
|
|
82
84
|
...(opts?.evaluationDefinitionKey
|
|
83
85
|
? { evaluationDefinitionKey: opts.evaluationDefinitionKey }
|
|
84
86
|
: {}),
|
|
87
|
+
...(scorerRevision ? { scorerRevision } : {}),
|
|
85
88
|
trial: buildTrialResult(agent.log, { ...evalResult, tokenUsage: deltaTokenUsage }, agent.scenarioEvidence, conversationTokens, conversationCost),
|
|
86
89
|
};
|
|
87
90
|
emitEvalResult({ result: recordedResult, agent });
|
|
@@ -120,6 +123,7 @@ async function fromSnapshot(snapshotPath, scorers, opts) {
|
|
|
120
123
|
};
|
|
121
124
|
const scoringStartedAt = performance.now();
|
|
122
125
|
const evalResult = await evaluateWithContext(ctx, scorers, { ...opts, llm: trackedLLM });
|
|
126
|
+
const scorerRevision = createScorerRevision(scorers);
|
|
123
127
|
const recordedResult = {
|
|
124
128
|
...evalResult,
|
|
125
129
|
resultKind: 'snapshot',
|
|
@@ -128,6 +132,7 @@ async function fromSnapshot(snapshotPath, scorers, opts) {
|
|
|
128
132
|
...(opts?.evaluationDefinitionKey
|
|
129
133
|
? { evaluationDefinitionKey: opts.evaluationDefinitionKey }
|
|
130
134
|
: {}),
|
|
135
|
+
...(scorerRevision ? { scorerRevision } : {}),
|
|
131
136
|
trial: buildTrialResult(snapshot.log, evalResult, snapshot.scenarioEvidence),
|
|
132
137
|
};
|
|
133
138
|
maybeThrowOnScorerErrors(recordedResult, opts?.onScorerError ?? 'skip');
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export { DEFAULT_COPY_IGNORE } from '../providers/copy-filter.js';
|
|
|
21
21
|
export { extractToolEventsFromLog } from '../tool-events.js';
|
|
22
22
|
export { collectSensitiveEnvValues, sanitizePersistenceValue, } from '../tool-event-results.js';
|
|
23
23
|
export { parseEnvFile } from '../utils/env.js';
|
|
24
|
+
export { parsePathgradeReport } from '../reporting/report-parser.js';
|
|
24
25
|
export { createAskBus, requireAskBusForLiveBatches, AskBusTimeoutError } from './ask-bus/bus.js';
|
|
25
26
|
export { toAskUserToolEvent } from './ask-bus/projection.js';
|
|
26
27
|
export type { AskUserToolEvent, AskUserToolEventArguments, AskUserToolEventQuestionArgument, } from './ask-bus/projection.js';
|
|
@@ -40,6 +41,7 @@ export type { DiagnosticsReport } from './diagnostics.js';
|
|
|
40
41
|
export type { ExpectedMcpStartupStatus, ExpectedMcpToolCall, McpStartupStatusEvidence, McpToolCallEvidence, McpApprovalEvidence, ExpectedMcpApproval, McpInvocationResult, } from './mcp-evidence.js';
|
|
41
42
|
export type { McpPolicyDenialReason, McpToolCallRequest, McpToolPolicyDecision, } from './mcp-safety.js';
|
|
42
43
|
export type { ToolEvent, McpToolCallClassification } from '../tool-events.js';
|
|
44
|
+
export type { ComparisonContract, ComparisonUnavailableReason, PathgradeGroupReport, PathgradeReport, StrippedTrialResult, } from '../types.js';
|
|
43
45
|
export type { LLMPort, EvalRuntime } from './eval-runtime.js';
|
|
44
46
|
export { createAgentLLM, createLLMClient, ProviderNotSupportedError } from '../utils/llm.js';
|
|
45
47
|
export type { CreateLLMClientOptions, LLMProviderAdapter, TokenUsage as LLMTokenUsage } from '../utils/llm.js';
|
package/dist/sdk/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export { DEFAULT_COPY_IGNORE } from '../providers/copy-filter.js';
|
|
|
19
19
|
export { extractToolEventsFromLog } from '../tool-events.js';
|
|
20
20
|
export { collectSensitiveEnvValues, sanitizePersistenceValue, } from '../tool-event-results.js';
|
|
21
21
|
export { parseEnvFile } from '../utils/env.js';
|
|
22
|
+
export { parsePathgradeReport } from '../reporting/report-parser.js';
|
|
22
23
|
export { createAskBus, requireAskBusForLiveBatches, AskBusTimeoutError } from './ask-bus/bus.js';
|
|
23
24
|
export { toAskUserToolEvent } from './ask-bus/projection.js';
|
|
24
25
|
export { buildAskBatchLogEntries } from './agent-result-log.js';
|
package/dist/sdk/lifecycle.js
CHANGED
|
@@ -81,6 +81,7 @@ function recordResult(result, agent, attribution) {
|
|
|
81
81
|
...(result.evaluationDefinitionKey
|
|
82
82
|
? { evaluationDefinitionKey: result.evaluationDefinitionKey }
|
|
83
83
|
: {}),
|
|
84
|
+
...(result.scorerRevision ? { scorerRevision: result.scorerRevision } : {}),
|
|
84
85
|
trial: result.trial,
|
|
85
86
|
resultKind: result.resultKind ?? 'evaluated',
|
|
86
87
|
...(result.scoringDurationMs !== undefined
|
package/dist/sdk/scorer-utils.js
CHANGED
|
@@ -2,10 +2,20 @@ export function clamp(n) {
|
|
|
2
2
|
return Math.max(0, Math.min(1, n));
|
|
3
3
|
}
|
|
4
4
|
export function getErrorMessage(error) {
|
|
5
|
-
if (error instanceof Error) {
|
|
6
|
-
return error
|
|
5
|
+
if (!(error instanceof Error)) {
|
|
6
|
+
return String(error);
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
let message = error.message;
|
|
10
|
+
let cause = error.cause;
|
|
11
|
+
while (cause instanceof Error && !seen.has(cause)) {
|
|
12
|
+
seen.add(cause);
|
|
13
|
+
if (cause.message && !message.includes(cause.message)) {
|
|
14
|
+
message = message ? `${message}: ${cause.message}` : cause.message;
|
|
15
|
+
}
|
|
16
|
+
cause = cause.cause;
|
|
17
|
+
}
|
|
18
|
+
return message || String(error);
|
|
9
19
|
}
|
|
10
20
|
export function matchesExpectation(event, expectation) {
|
|
11
21
|
if (event.action !== expectation.action)
|
package/dist/sdk/types.d.ts
CHANGED
|
@@ -372,6 +372,8 @@ export interface RecordedEvalResult extends Omit<EvalResult, 'score'> {
|
|
|
372
372
|
scoringDurationMs?: number;
|
|
373
373
|
recordedAt?: string;
|
|
374
374
|
evaluationDefinitionKey?: string;
|
|
375
|
+
/** Privacy-safe revision of the declared scorers used by this evaluation. */
|
|
376
|
+
scorerRevision?: string;
|
|
375
377
|
}
|
|
376
378
|
export type EvaluationResultKind = 'evaluated' | 'synthetic_no_evaluation' | 'snapshot';
|
|
377
379
|
export interface ScorerResultEntry {
|
|
@@ -393,6 +395,7 @@ export interface PathgradeTestMeta {
|
|
|
393
395
|
scorers: ScorerResultEntry[];
|
|
394
396
|
/** Stable identity for this evaluation definition across separate runs. */
|
|
395
397
|
evaluationDefinitionKey?: string;
|
|
398
|
+
scorerRevision?: string;
|
|
396
399
|
trial?: TrialResult;
|
|
397
400
|
diagnostics?: DiagnosticsReport;
|
|
398
401
|
resultKind?: EvaluationResultKind;
|
package/dist/types.d.ts
CHANGED
|
@@ -219,6 +219,16 @@ export interface EvalReport {
|
|
|
219
219
|
trials: TrialResult[];
|
|
220
220
|
skills_used: string[];
|
|
221
221
|
}
|
|
222
|
+
export type ComparisonUnavailableReason = 'definition_metadata_missing' | 'scorer_metadata_missing' | 'runtime_metadata_missing' | 'sampling_incomplete';
|
|
223
|
+
export interface ComparisonContract {
|
|
224
|
+
version: 1;
|
|
225
|
+
definition_revision?: string;
|
|
226
|
+
scorer_revision?: string;
|
|
227
|
+
runtime_revision?: string;
|
|
228
|
+
sampling_revision?: string;
|
|
229
|
+
report_schema_revision: 'pathgrade-results-v2';
|
|
230
|
+
unavailable_reasons?: ComparisonUnavailableReason[];
|
|
231
|
+
}
|
|
222
232
|
/**
|
|
223
233
|
* TrialResult with `session_log` and `conversation` stripped. These fields
|
|
224
234
|
* live only in the per-group trace files; the consolidated results.json keeps
|
|
@@ -232,6 +242,8 @@ export type StrippedTrialResult = Omit<TrialResult, 'session_log' | 'conversatio
|
|
|
232
242
|
export type PathgradeGroupReport = Omit<EvalReport, 'trials' | 'status'> & {
|
|
233
243
|
status?: 'pass' | 'fail';
|
|
234
244
|
trials: StrippedTrialResult[];
|
|
245
|
+
source_file?: string;
|
|
246
|
+
comparison_contract?: ComparisonContract;
|
|
235
247
|
/** Relative path from `.pathgrade/` to the trace file for this group. */
|
|
236
248
|
trace_file: string;
|
|
237
249
|
};
|
|
@@ -277,6 +289,8 @@ export interface PathgradeReport {
|
|
|
277
289
|
* in every group passed its vitest test.
|
|
278
290
|
*/
|
|
279
291
|
status: 'pass' | 'fail';
|
|
292
|
+
/** Agent Evals compatibility manifest when changed selection finds no runnable evals. */
|
|
293
|
+
run_kind?: 'evaluation' | 'no-affected';
|
|
280
294
|
groups: PathgradeGroupReport[];
|
|
281
295
|
/**
|
|
282
296
|
* Present when `pathgrade run --changed` produced the run. Absent on
|
|
@@ -73,7 +73,7 @@ async function postAnthropic(apiKey, useCache, body, env) {
|
|
|
73
73
|
});
|
|
74
74
|
if (!response.ok) {
|
|
75
75
|
const errBody = await response.text().catch(() => '');
|
|
76
|
-
throw new Error(`Anthropic API error (${response.status}): ${errBody.slice(0, 300)}`);
|
|
76
|
+
throw new Error(`Anthropic API error (${response.status}) for model ${String(body.model)}: ${errBody.slice(0, 300)}`);
|
|
77
77
|
}
|
|
78
78
|
return await response.json();
|
|
79
79
|
}
|
|
@@ -116,7 +116,10 @@ export const anthropicProvider = {
|
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
catch (error) {
|
|
119
|
-
|
|
119
|
+
// Reporting seams only propagate error.message, never error.cause.
|
|
120
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
121
|
+
const modelTag = reason.includes(model) ? '' : ` (model ${model})`;
|
|
122
|
+
throw new Error(`Anthropic API call failed${modelTag}: ${reason}`, { cause: error });
|
|
120
123
|
}
|
|
121
124
|
},
|
|
122
125
|
async callWithTools(messages, opts) {
|
package/dist/utils/llm.js
CHANGED
|
@@ -114,6 +114,7 @@ export function createLLMClient(arg, legacyAgentName) {
|
|
|
114
114
|
const toTry = narrowed.length > 0 ? narrowed : candidates;
|
|
115
115
|
let lastError;
|
|
116
116
|
let lastFailed;
|
|
117
|
+
const failures = [];
|
|
117
118
|
for (const provider of toTry) {
|
|
118
119
|
if (!await provider.isAvailable(opts.env))
|
|
119
120
|
continue;
|
|
@@ -128,6 +129,7 @@ export function createLLMClient(arg, legacyAgentName) {
|
|
|
128
129
|
catch (error) {
|
|
129
130
|
lastError = error;
|
|
130
131
|
lastFailed = { name: provider.name, reason: lastError.message };
|
|
132
|
+
failures.push(lastFailed);
|
|
131
133
|
// Fall through to next provider
|
|
132
134
|
}
|
|
133
135
|
}
|
|
@@ -149,11 +151,15 @@ export function createLLMClient(arg, legacyAgentName) {
|
|
|
149
151
|
catch (error) {
|
|
150
152
|
lastError = error;
|
|
151
153
|
lastFailed = { name: provider.name, reason: lastError.message };
|
|
154
|
+
failures.push(lastFailed);
|
|
152
155
|
}
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
|
-
if (lastError)
|
|
156
|
-
|
|
158
|
+
if (lastError) {
|
|
159
|
+
if (failures.length <= 1)
|
|
160
|
+
throw lastError;
|
|
161
|
+
throw new Error(`All LLM providers failed: ${failures.map((f) => `${f.name}: ${f.reason}`).join('; ')}`, { cause: lastError });
|
|
162
|
+
}
|
|
157
163
|
if (agentName === 'claude') {
|
|
158
164
|
throw new Error('Claude CLI not found on PATH. Install with: npm install -g @anthropic-ai/claude-code');
|
|
159
165
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/pathgrade",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.31",
|
|
4
4
|
"packageManager": "yarn@4.12.0",
|
|
5
5
|
"description": "Evaluate whether AI agents discover and use your skills correctly",
|
|
6
6
|
"exports": {
|
|
@@ -140,5 +140,5 @@
|
|
|
140
140
|
"typescript": "^5.9.3",
|
|
141
141
|
"zod": "4.3.6"
|
|
142
142
|
},
|
|
143
|
-
"falconPackageHash": "
|
|
143
|
+
"falconPackageHash": "1f94e7e2c7f2f999b9ba92d6b76b362a5adf549800b96e08994d6c49"
|
|
144
144
|
}
|