@wix/pathgrade 0.37.0 → 1.0.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 +116 -32
- package/dist/adapter-kit/index.d.ts +17 -0
- package/dist/adapter-kit/index.js +14 -0
- package/dist/adapters/jest/index.d.ts +3 -0
- package/dist/adapters/jest/index.js +3 -0
- package/dist/adapters/jest/invocation-adapter.d.ts +15 -0
- package/dist/adapters/jest/invocation-adapter.js +115 -0
- package/dist/adapters/jest/lifecycle.d.ts +21 -0
- package/dist/adapters/jest/lifecycle.js +62 -0
- package/dist/adapters/jest/metadata.d.ts +6 -0
- package/dist/adapters/jest/metadata.js +35 -0
- package/dist/adapters/jest/reporter.cjs +10 -0
- package/dist/adapters/jest/reporter.d.ts +5 -0
- package/dist/adapters/jest/reporter.js +67 -0
- package/dist/adapters/jest/results.d.ts +31 -0
- package/dist/adapters/jest/results.js +144 -0
- package/dist/adapters/jest/runner-adapter.d.ts +9 -0
- package/dist/adapters/jest/runner-adapter.js +49 -0
- package/dist/adapters/jest/setup.d.ts +1 -0
- package/dist/adapters/jest/setup.js +10 -0
- package/dist/adapters/node-test/index.d.ts +8 -0
- package/dist/adapters/node-test/index.js +106 -0
- package/dist/adapters/node-test/invocation-adapter.d.ts +5 -0
- package/dist/adapters/node-test/invocation-adapter.js +47 -0
- package/dist/adapters/node-test/runner-adapter.d.ts +2 -0
- package/dist/adapters/node-test/runner-adapter.js +116 -0
- package/dist/adapters/vitest/index.d.ts +9 -0
- package/dist/adapters/vitest/index.js +82 -0
- package/dist/adapters/vitest/lifecycle.d.ts +24 -0
- package/dist/adapters/vitest/lifecycle.js +41 -0
- package/dist/adapters/vitest/reporter.d.ts +12 -0
- package/dist/adapters/vitest/reporter.js +75 -0
- package/dist/adapters/vitest/setup.d.ts +1 -0
- package/dist/adapters/vitest/setup.js +3 -0
- package/dist/agents/codex-app-server/agent.js +5 -1
- package/dist/agents/codex-app-server/protocol/index.js +3 -3
- package/dist/agents/codex-app-server/turn-completion.d.ts +1 -0
- package/dist/agents/codex-app-server/turn-completion.js +9 -0
- package/dist/commands/affected.js +22 -15
- package/dist/commands/run-args.d.ts +4 -3
- package/dist/commands/run-args.js +12 -6
- package/dist/commands/run-changed.d.ts +4 -4
- package/dist/commands/run-changed.js +45 -58
- package/dist/config/pathgrade.d.ts +48 -0
- package/dist/config/pathgrade.js +204 -0
- package/dist/pathgrade.d.ts +2 -2
- package/dist/pathgrade.js +35 -34
- package/dist/plugin/index.d.ts +2 -9
- package/dist/plugin/index.js +1 -81
- package/dist/plugin/lifecycle.d.ts +1 -45
- package/dist/plugin/lifecycle.js +1 -146
- package/dist/plugin/reporter.d.ts +1 -37
- package/dist/plugin/reporter.js +1 -280
- package/dist/plugin/setup.d.ts +1 -1
- package/dist/plugin/setup.js +1 -3
- package/dist/providers/copy-filter.d.ts +5 -0
- package/dist/providers/copy-filter.js +9 -0
- package/dist/providers/sandbox.js +2 -2
- package/dist/providers/workspace.d.ts +6 -0
- package/dist/providers/workspace.js +34 -14
- package/dist/reporters/diagnostics.d.ts +2 -40
- package/dist/reporters/diagnostics.js +1 -80
- package/dist/reporters/report-summary.d.ts +6 -0
- package/dist/reporters/report-summary.js +29 -0
- package/dist/reporting/artifacts.d.ts +2 -0
- package/dist/reporting/artifacts.js +20 -0
- package/dist/reporting/core.d.ts +2 -0
- package/dist/reporting/core.js +173 -0
- package/dist/reporting/types.d.ts +57 -0
- package/dist/reporting/types.js +1 -0
- package/dist/reporting/vitest-edge.d.ts +1 -0
- package/dist/reporting/vitest-edge.js +1 -0
- package/dist/runners/adapter-loader.d.ts +17 -0
- package/dist/runners/adapter-loader.js +67 -0
- package/dist/runners/adapter.d.ts +65 -0
- package/dist/runners/adapter.js +1 -0
- package/dist/runners/invocation.d.ts +10 -0
- package/dist/runners/invocation.js +1 -0
- package/dist/runners/lifecycle-hooks.d.ts +2 -0
- package/dist/runners/lifecycle-hooks.js +18 -0
- package/dist/runners/model-builders.d.ts +4 -0
- package/dist/runners/model-builders.js +67 -0
- package/dist/runners/model-validation.d.ts +12 -0
- package/dist/runners/model-validation.js +213 -0
- package/dist/runners/model.d.ts +117 -0
- package/dist/runners/model.js +1 -0
- package/dist/runners/orchestrator.d.ts +30 -0
- package/dist/runners/orchestrator.js +74 -0
- package/dist/runners/report-projection.d.ts +6 -0
- package/dist/runners/report-projection.js +62 -0
- package/dist/runners/selection.d.ts +6 -0
- package/dist/runners/selection.js +10 -0
- package/dist/runners/vitest-adapter.d.ts +8 -0
- package/dist/runners/vitest-adapter.js +108 -0
- package/dist/runners/vitest-invocation.d.ts +10 -0
- package/dist/runners/vitest-invocation.js +44 -0
- package/dist/runners/vitest-lifecycle.d.ts +43 -0
- package/dist/runners/vitest-lifecycle.js +150 -0
- package/dist/sdk/agent.js +15 -16
- package/dist/sdk/case-context.d.ts +20 -0
- package/dist/sdk/case-context.js +31 -0
- package/dist/sdk/diagnostics.d.ts +40 -0
- package/dist/sdk/diagnostics.js +80 -0
- package/dist/sdk/eval-runtime.d.ts +4 -0
- package/dist/sdk/eval-runtime.js +11 -1
- package/dist/sdk/evaluate.js +2 -1
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +1 -0
- package/dist/sdk/lifecycle.d.ts +40 -0
- package/dist/sdk/lifecycle.js +170 -0
- package/dist/sdk/result-capture.d.ts +18 -0
- package/dist/sdk/result-capture.js +49 -0
- package/dist/sdk/types.d.ts +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/utils/llm-providers/anthropic.js +9 -2
- package/package.json +56 -4
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { createJiti } from 'jiti';
|
|
4
|
+
export const DEFAULT_EVAL_INCLUDE = ['**/*.eval.ts'];
|
|
5
|
+
export const DEFAULT_EVAL_EXCLUDE = [
|
|
6
|
+
'**/node_modules/**',
|
|
7
|
+
'**/.git/**',
|
|
8
|
+
'.worktrees/**',
|
|
9
|
+
'worktrees/**',
|
|
10
|
+
'**/fixtures/**',
|
|
11
|
+
];
|
|
12
|
+
const PATHGRADE_CONFIG_CANDIDATES = [
|
|
13
|
+
'pathgrade.config.ts',
|
|
14
|
+
'pathgrade.config.mts',
|
|
15
|
+
'pathgrade.config.js',
|
|
16
|
+
'pathgrade.config.mjs',
|
|
17
|
+
];
|
|
18
|
+
export function defaultPathgradeConfig() {
|
|
19
|
+
return {
|
|
20
|
+
runner: {
|
|
21
|
+
adapter: 'vitest',
|
|
22
|
+
args: [],
|
|
23
|
+
},
|
|
24
|
+
evals: {
|
|
25
|
+
include: [...DEFAULT_EVAL_INCLUDE],
|
|
26
|
+
exclude: [...DEFAULT_EVAL_EXCLUDE],
|
|
27
|
+
},
|
|
28
|
+
affected: {
|
|
29
|
+
global: [],
|
|
30
|
+
},
|
|
31
|
+
diagnostics: false,
|
|
32
|
+
verbose: false,
|
|
33
|
+
ci: {},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export async function resolvePathgradeConfig(input) {
|
|
37
|
+
const fileConfig = await loadPathgradeConfigFile(input.cwd, input.configPath);
|
|
38
|
+
const legacyConfig = await loadLegacyVitestConfig(input.cwd, input.legacyVitestConfigPath, input.warn);
|
|
39
|
+
return mergePathgradeConfig(mergePathgradeConfig(mergePathgradeConfig(defaultPathgradeConfig(), legacyConfig), fileConfig), input.cli);
|
|
40
|
+
}
|
|
41
|
+
function mergePathgradeConfig(base, override) {
|
|
42
|
+
if (!override)
|
|
43
|
+
return base;
|
|
44
|
+
return {
|
|
45
|
+
runner: {
|
|
46
|
+
adapter: override.runner?.adapter ?? base.runner.adapter,
|
|
47
|
+
args: override.runner?.args ?? base.runner.args,
|
|
48
|
+
},
|
|
49
|
+
evals: {
|
|
50
|
+
include: override.evals?.include ?? base.evals.include,
|
|
51
|
+
exclude: override.evals?.exclude ?? base.evals.exclude,
|
|
52
|
+
},
|
|
53
|
+
affected: {
|
|
54
|
+
global: override.affected?.global ?? base.affected.global,
|
|
55
|
+
},
|
|
56
|
+
reporter: override.reporter ?? base.reporter,
|
|
57
|
+
diagnostics: override.diagnostics ?? base.diagnostics,
|
|
58
|
+
verbose: override.verbose ?? base.verbose,
|
|
59
|
+
ci: {
|
|
60
|
+
threshold: override.ci?.threshold ?? base.ci.threshold,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function loadPathgradeConfigFile(cwd, configPath) {
|
|
65
|
+
const resolved = configPath
|
|
66
|
+
? path.resolve(cwd, configPath)
|
|
67
|
+
: PATHGRADE_CONFIG_CANDIDATES
|
|
68
|
+
.map(candidate => path.join(cwd, candidate))
|
|
69
|
+
.find(candidate => fs.existsSync(candidate));
|
|
70
|
+
if (!resolved)
|
|
71
|
+
return undefined;
|
|
72
|
+
try {
|
|
73
|
+
const jiti = createJiti(cwd, { interopDefault: true });
|
|
74
|
+
const loaded = await jiti.import(resolved, { default: true });
|
|
75
|
+
return validatePathgradeConfig(loaded, path.relative(cwd, resolved));
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
if (err instanceof InvalidPathgradeConfigError) {
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`pathgrade: failed to load ${path.relative(cwd, resolved)}: ${errMsg(err)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function isObject(value) {
|
|
85
|
+
return value !== null && typeof value === 'object';
|
|
86
|
+
}
|
|
87
|
+
class InvalidPathgradeConfigError extends Error {
|
|
88
|
+
}
|
|
89
|
+
function validatePathgradeConfig(value, label) {
|
|
90
|
+
if (!isObject(value)) {
|
|
91
|
+
throw invalidConfig(label, 'default export must be an object');
|
|
92
|
+
}
|
|
93
|
+
validateOptionalObject(value.runner, label, 'runner');
|
|
94
|
+
const runner = asOptionalObject(value.runner);
|
|
95
|
+
validateOptionalString(runner?.adapter, label, 'runner.adapter');
|
|
96
|
+
validateOptionalStringArray(runner?.args, label, 'runner.args');
|
|
97
|
+
validateOptionalObject(value.evals, label, 'evals');
|
|
98
|
+
const evals = asOptionalObject(value.evals);
|
|
99
|
+
validateOptionalStringArray(evals?.include, label, 'evals.include');
|
|
100
|
+
validateOptionalStringArray(evals?.exclude, label, 'evals.exclude');
|
|
101
|
+
validateOptionalObject(value.affected, label, 'affected');
|
|
102
|
+
const affected = asOptionalObject(value.affected);
|
|
103
|
+
validateOptionalStringArray(affected?.global, label, 'affected.global');
|
|
104
|
+
if (value.reporter !== undefined && !['cli', 'browser', 'json'].includes(String(value.reporter))) {
|
|
105
|
+
throw invalidConfig(label, 'reporter must be one of cli, browser, or json');
|
|
106
|
+
}
|
|
107
|
+
validateOptionalBoolean(value.diagnostics, label, 'diagnostics');
|
|
108
|
+
validateOptionalBoolean(value.verbose, label, 'verbose');
|
|
109
|
+
validateOptionalObject(value.ci, label, 'ci');
|
|
110
|
+
const ci = asOptionalObject(value.ci);
|
|
111
|
+
validateOptionalNumber(ci?.threshold, label, 'ci.threshold');
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
function asOptionalObject(value) {
|
|
115
|
+
return isObject(value) ? value : undefined;
|
|
116
|
+
}
|
|
117
|
+
function validateOptionalObject(value, label, field) {
|
|
118
|
+
if (value !== undefined && !isObject(value)) {
|
|
119
|
+
throw invalidConfig(label, `${field} must be an object`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function validateOptionalString(value, label, field) {
|
|
123
|
+
if (value !== undefined && typeof value !== 'string') {
|
|
124
|
+
throw invalidConfig(label, `${field} must be a string`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function validateOptionalStringArray(value, label, field) {
|
|
128
|
+
if (value !== undefined && (!Array.isArray(value) || value.some(item => typeof item !== 'string'))) {
|
|
129
|
+
throw invalidConfig(label, `${field} must be an array of strings`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function validateOptionalBoolean(value, label, field) {
|
|
133
|
+
if (value !== undefined && typeof value !== 'boolean') {
|
|
134
|
+
throw invalidConfig(label, `${field} must be a boolean`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function validateOptionalNumber(value, label, field) {
|
|
138
|
+
if (value !== undefined && typeof value !== 'number') {
|
|
139
|
+
throw invalidConfig(label, `${field} must be a number`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function invalidConfig(label, reason) {
|
|
143
|
+
return new InvalidPathgradeConfigError(`pathgrade: invalid ${label}: ${reason}`);
|
|
144
|
+
}
|
|
145
|
+
function errMsg(err) {
|
|
146
|
+
return err instanceof Error ? err.message : String(err);
|
|
147
|
+
}
|
|
148
|
+
const VITEST_CONFIG_CANDIDATES = [
|
|
149
|
+
'vitest.config.ts',
|
|
150
|
+
'vitest.config.mts',
|
|
151
|
+
'vitest.config.js',
|
|
152
|
+
'vitest.config.mjs',
|
|
153
|
+
];
|
|
154
|
+
async function loadLegacyVitestConfig(cwd, configPath, warn = () => { }) {
|
|
155
|
+
const resolved = configPath
|
|
156
|
+
? path.resolve(cwd, configPath)
|
|
157
|
+
: VITEST_CONFIG_CANDIDATES
|
|
158
|
+
.map(candidate => path.join(cwd, candidate))
|
|
159
|
+
.find(candidate => fs.existsSync(candidate));
|
|
160
|
+
if (!resolved)
|
|
161
|
+
return undefined;
|
|
162
|
+
let loaded;
|
|
163
|
+
try {
|
|
164
|
+
const jiti = createJiti(cwd, { interopDefault: true });
|
|
165
|
+
loaded = await jiti.import(resolved, { default: true });
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
const message = `pathgrade: failed to load ${path.relative(cwd, resolved)}: ${errMsg(err)}`;
|
|
169
|
+
if (configPath)
|
|
170
|
+
throw new Error(message);
|
|
171
|
+
warn(message);
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
const plugins = findPluginsList(loaded);
|
|
175
|
+
const pathgradePlugin = plugins?.find(plugin => isPathgradePlugin(plugin));
|
|
176
|
+
const opts = isObject(pathgradePlugin)
|
|
177
|
+
? pathgradePlugin.__pathgradeOptions
|
|
178
|
+
: undefined;
|
|
179
|
+
if (!isObject(opts))
|
|
180
|
+
return undefined;
|
|
181
|
+
return {
|
|
182
|
+
evals: {
|
|
183
|
+
...(Array.isArray(opts.include) ? { include: opts.include } : {}),
|
|
184
|
+
...(Array.isArray(opts.exclude) ? { exclude: opts.exclude } : {}),
|
|
185
|
+
},
|
|
186
|
+
affected: {
|
|
187
|
+
global: isObject(opts.affected) && Array.isArray(opts.affected.global)
|
|
188
|
+
? opts.affected.global
|
|
189
|
+
: [],
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function findPluginsList(config) {
|
|
194
|
+
if (!isObject(config))
|
|
195
|
+
return null;
|
|
196
|
+
if (Array.isArray(config.plugins))
|
|
197
|
+
return config.plugins;
|
|
198
|
+
if (isObject(config.test) && Array.isArray(config.test.plugins))
|
|
199
|
+
return config.test.plugins;
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
function isPathgradePlugin(plugin) {
|
|
203
|
+
return isObject(plugin) && (plugin.name === 'pathgrade' || '__pathgradeOptions' in plugin);
|
|
204
|
+
}
|
package/dist/pathgrade.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* pathgrade CLI —
|
|
3
|
+
* pathgrade CLI — runner-neutral wrapper around Pathgrade adapters
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* pathgrade run [--
|
|
6
|
+
* pathgrade run [-- runner-args] Run evals via the selected adapter
|
|
7
7
|
* pathgrade init [--force] Generate eval scaffolding
|
|
8
8
|
* pathgrade preview [browser] View results (CLI default, or browser)
|
|
9
9
|
*/
|
package/dist/pathgrade.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* pathgrade CLI —
|
|
3
|
+
* pathgrade CLI — runner-neutral wrapper around Pathgrade adapters
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* pathgrade run [--
|
|
6
|
+
* pathgrade run [-- runner-args] Run evals via the selected adapter
|
|
7
7
|
* pathgrade init [--force] Generate eval scaffolding
|
|
8
8
|
* pathgrade preview [browser] View results (CLI default, or browser)
|
|
9
9
|
*/
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
|
-
import { spawn } from 'child_process';
|
|
13
12
|
import { runInit } from './commands/init.js';
|
|
14
13
|
import { runAnalyze } from './commands/analyze.js';
|
|
15
14
|
import { runValidate, runValidateAffected } from './commands/validate.js';
|
|
@@ -20,6 +19,8 @@ import { runReport } from './commands/report.js';
|
|
|
20
19
|
import { runAffected } from './commands/affected.js';
|
|
21
20
|
import { runChanged } from './commands/run-changed.js';
|
|
22
21
|
import { clearSidecar } from './affected/sidecar.js';
|
|
22
|
+
import { resolvePathgradeConfig } from './config/pathgrade.js';
|
|
23
|
+
import { loadRunnerInvocationAdapter } from './runners/adapter-loader.js';
|
|
23
24
|
import { fmt } from './utils/cli.js';
|
|
24
25
|
import { shutdown } from './utils/shutdown.js';
|
|
25
26
|
function loadDotenv() {
|
|
@@ -52,7 +53,7 @@ function validateApiKeys() {
|
|
|
52
53
|
const hasClaude = !!process.env.HOME; // Claude CLI uses OS keychain, just check it exists
|
|
53
54
|
if (!hasAnthropic && !hasOpenAI) {
|
|
54
55
|
console.log(`\n ${fmt.dim('warning:')} No API keys found. Set ANTHROPIC_API_KEY or OPENAI_API_KEY in .env or environment.\n` +
|
|
55
|
-
` ${fmt.dim(' Claude CLI auth (keychain) and Codex cached login (~/.codex/auth.json) may still work if installed.')}\n`);
|
|
56
|
+
` ${fmt.dim(' Claude CLI auth (keychain) and Codex exec cached login (~/.codex/auth.json) may still work if installed.')}\n`);
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
async function main() {
|
|
@@ -144,7 +145,7 @@ async function main() {
|
|
|
144
145
|
return;
|
|
145
146
|
}
|
|
146
147
|
if (command === 'run' || !command || command.startsWith('-')) {
|
|
147
|
-
// pathgrade run [--changed [--since=…|--changed-files=…]] [--] [
|
|
148
|
+
// pathgrade run [--changed [--since=…|--changed-files=…]] [--] [runner-args]
|
|
148
149
|
loadDotenv();
|
|
149
150
|
validateApiKeys();
|
|
150
151
|
const parsed = parsePathgradeRunArgs(command === 'run' ? args.slice(1) : args);
|
|
@@ -155,18 +156,6 @@ async function main() {
|
|
|
155
156
|
const exitCode = await runChanged({
|
|
156
157
|
cwd: process.cwd(),
|
|
157
158
|
parsed,
|
|
158
|
-
spawnVitest: ({ argv }) => new Promise(resolve => {
|
|
159
|
-
const child = spawn('npx', ['vitest', ...argv], {
|
|
160
|
-
stdio: 'inherit',
|
|
161
|
-
env: {
|
|
162
|
-
...process.env,
|
|
163
|
-
...(parsed.forceDiagnostics ? { PATHGRADE_DIAGNOSTICS: '1' } : {}),
|
|
164
|
-
...(parsed.forceVerbose ? { PATHGRADE_VERBOSE: '1' } : {}),
|
|
165
|
-
},
|
|
166
|
-
shell: true,
|
|
167
|
-
});
|
|
168
|
-
child.on('close', code => resolve(code ?? 0));
|
|
169
|
-
}),
|
|
170
159
|
});
|
|
171
160
|
process.exitCode = exitCode;
|
|
172
161
|
return;
|
|
@@ -175,18 +164,28 @@ async function main() {
|
|
|
175
164
|
// previous `--changed` run so it doesn't leak into this full-suite
|
|
176
165
|
// run (the reporter would otherwise merge old metadata).
|
|
177
166
|
await clearSidecar(process.cwd());
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
167
|
+
const env = {
|
|
168
|
+
...process.env,
|
|
169
|
+
...(parsed.forceDiagnostics ? { PATHGRADE_DIAGNOSTICS: '1' } : {}),
|
|
170
|
+
...(parsed.forceVerbose ? { PATHGRADE_VERBOSE: '1' } : {}),
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
const config = await resolvePathgradeConfig({ cwd: process.cwd() });
|
|
174
|
+
const runner = await loadRunnerInvocationAdapter({
|
|
175
|
+
adapterName: parsed.adapterName ?? config.runner.adapter,
|
|
176
|
+
cwd: process.cwd(),
|
|
177
|
+
config,
|
|
178
|
+
});
|
|
179
|
+
process.exitCode = await runner.run({
|
|
180
|
+
cwd: process.cwd(),
|
|
181
|
+
runnerArgs: [...config.runner.args, ...parsed.runnerArgs],
|
|
182
|
+
env,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
187
|
+
process.exitCode = 1;
|
|
188
|
+
}
|
|
190
189
|
return;
|
|
191
190
|
}
|
|
192
191
|
console.error(`Unknown command: ${command}`);
|
|
@@ -195,13 +194,15 @@ async function main() {
|
|
|
195
194
|
}
|
|
196
195
|
function printHelp() {
|
|
197
196
|
console.log(`
|
|
198
|
-
pathgrade - Evaluate AI agent skills with
|
|
197
|
+
pathgrade - Evaluate AI agent skills with a runner adapter
|
|
199
198
|
|
|
200
199
|
Usage:
|
|
201
|
-
pathgrade run [--
|
|
200
|
+
pathgrade run [-- runner-args] Run evals (loads .env, delegates to the selected adapter)
|
|
202
201
|
[--changed] Run only evals affected by the current PR/change-set
|
|
203
202
|
[--since=<ref>] Override base ref (implies git mode)
|
|
204
203
|
[--changed-files=<path>] Use an explicit newline-delimited file list
|
|
204
|
+
[--adapter=<name|path>] Select built-in or third-party runner adapter
|
|
205
|
+
[--diagnostics] Print full diagnostics for passing evals too
|
|
205
206
|
[--quiet] Suppress the run-start summary
|
|
206
207
|
[--verbose|-v] Stream live per-turn events to stderr during the run
|
|
207
208
|
pathgrade init [--force] Generate eval scaffolding
|
|
@@ -223,9 +224,9 @@ function printHelp() {
|
|
|
223
224
|
[--json] Emit structured JSON (snake_case) to stdout
|
|
224
225
|
|
|
225
226
|
Environment:
|
|
226
|
-
PATHGRADE_AGENT=codex
|
|
227
|
-
ANTHROPIC_API_KEY API key for Claude
|
|
228
|
-
OPENAI_API_KEY API key for Codex
|
|
227
|
+
PATHGRADE_AGENT=codex Fallback agent when createAgent({ agent }) is omitted
|
|
228
|
+
ANTHROPIC_API_KEY API key for Claude and Anthropic-backed judges
|
|
229
|
+
OPENAI_API_KEY API key for Codex and OpenAI-backed judges
|
|
229
230
|
|
|
230
231
|
Examples:
|
|
231
232
|
pathgrade run # run all *.eval.ts files
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export type { PathgradePluginOptions };
|
|
3
|
-
/**
|
|
4
|
-
* Vitest plugin for pathgrade evaluations.
|
|
5
|
-
*
|
|
6
|
-
* Configures test file patterns, timeout, auto-dispose lifecycle hooks,
|
|
7
|
-
* and the pathgrade reporter for aggregate statistics.
|
|
8
|
-
*/
|
|
9
|
-
export declare function pathgrade(opts?: PathgradePluginOptions): any;
|
|
1
|
+
export { pathgrade } from '../adapters/vitest/index.js';
|
|
2
|
+
export type { PathgradePluginOptions } from '../adapters/vitest/index.js';
|
package/dist/plugin/index.js
CHANGED
|
@@ -1,81 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { configDefaults } from 'vitest/config';
|
|
4
|
-
import { PathgradeReporter } from './reporter.js';
|
|
5
|
-
import { discoverPathgradeEvalFiles } from '../evals/discovery.js';
|
|
6
|
-
const DEFAULT_EXCLUDE = [...configDefaults.exclude, '.worktrees/**', 'worktrees/**'];
|
|
7
|
-
function resolveSetupFile() {
|
|
8
|
-
// Prefer .ts (running from source via vitest) over .js (built dist)
|
|
9
|
-
const tsPath = path.resolve(import.meta.dirname, 'setup.ts');
|
|
10
|
-
if (fs.existsSync(tsPath))
|
|
11
|
-
return tsPath;
|
|
12
|
-
return path.resolve(import.meta.dirname, 'setup.js');
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Vitest plugin for pathgrade evaluations.
|
|
16
|
-
*
|
|
17
|
-
* Configures test file patterns, timeout, auto-dispose lifecycle hooks,
|
|
18
|
-
* and the pathgrade reporter for aggregate statistics.
|
|
19
|
-
*/
|
|
20
|
-
export function pathgrade(opts) {
|
|
21
|
-
const timeoutSec = opts?.timeout ?? 300;
|
|
22
|
-
// Verbose live-streaming seam: the SDK (createAgent) reads
|
|
23
|
-
// `process.env.PATHGRADE_VERBOSE` to decide whether to emit. Wiring the
|
|
24
|
-
// plugin option through the env var keeps a single source of truth and
|
|
25
|
-
// preserves CLI/env precedence — we only set the env if the user hasn't
|
|
26
|
-
// already set it via their shell or the `pathgrade run --verbose` CLI.
|
|
27
|
-
if (opts?.verbose === true && process.env.PATHGRADE_VERBOSE === undefined) {
|
|
28
|
-
process.env.PATHGRADE_VERBOSE = '1';
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
name: 'pathgrade',
|
|
32
|
-
// Surface the user's options to pre-vitest CLI commands
|
|
33
|
-
// (`pathgrade affected`, `pathgrade run --changed`) so they can
|
|
34
|
-
// read `affected.global` after loading vitest.config.ts.
|
|
35
|
-
// See `src/affected/config.ts`.
|
|
36
|
-
__pathgradeOptions: opts ?? {},
|
|
37
|
-
config() {
|
|
38
|
-
return {
|
|
39
|
-
resolve: {
|
|
40
|
-
alias: resolveLocalAliases(),
|
|
41
|
-
},
|
|
42
|
-
test: {
|
|
43
|
-
include: opts?.include ?? ['**/*.eval.ts'],
|
|
44
|
-
exclude: opts?.exclude ?? DEFAULT_EXCLUDE,
|
|
45
|
-
testTimeout: (timeoutSec + 30) * 1000,
|
|
46
|
-
setupFiles: [resolveSetupFile()],
|
|
47
|
-
reporters: [
|
|
48
|
-
'default',
|
|
49
|
-
new PathgradeReporter(opts),
|
|
50
|
-
],
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
},
|
|
54
|
-
configResolved(resolved) {
|
|
55
|
-
const root = resolved.root ?? process.cwd();
|
|
56
|
-
const include = resolved.test?.include ?? opts?.include ?? ['**/*.eval.ts'];
|
|
57
|
-
const exclude = resolved.test?.exclude ?? opts?.exclude ?? DEFAULT_EXCLUDE;
|
|
58
|
-
const discovered = discoverPathgradeEvalFiles({ cwd: root, include, exclude });
|
|
59
|
-
if (!resolved.test) {
|
|
60
|
-
resolved.test = {};
|
|
61
|
-
}
|
|
62
|
-
resolved.test.include = discovered;
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* When running from source (not installed via npm), resolve
|
|
68
|
-
* `@wix/pathgrade` imports to the local src/ directory so examples
|
|
69
|
-
* can use canonical package imports while developing locally.
|
|
70
|
-
*/
|
|
71
|
-
function resolveLocalAliases() {
|
|
72
|
-
const sdkSource = path.resolve(import.meta.dirname, '..', 'sdk', 'index.ts');
|
|
73
|
-
if (!fs.existsSync(sdkSource))
|
|
74
|
-
return undefined;
|
|
75
|
-
const src = path.resolve(import.meta.dirname, '..');
|
|
76
|
-
return {
|
|
77
|
-
'@wix/pathgrade/mcp-mock': path.join(src, 'core', 'mcp-mock.ts'),
|
|
78
|
-
'@wix/pathgrade/plugin': path.join(src, 'plugin', 'index.ts'),
|
|
79
|
-
'@wix/pathgrade': path.join(src, 'sdk', 'index.ts'),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
1
|
+
export { pathgrade } from '../adapters/vitest/index.js';
|
|
@@ -1,45 +1 @@
|
|
|
1
|
-
|
|
2
|
-
declare module 'vitest' {
|
|
3
|
-
interface TaskMeta {
|
|
4
|
-
pathgrade?: PathgradeTestMeta[];
|
|
5
|
-
}
|
|
6
|
-
}
|
|
7
|
-
type AfterEachFn = (fn: (ctx: {
|
|
8
|
-
task: {
|
|
9
|
-
id: string;
|
|
10
|
-
meta: Record<string, unknown>;
|
|
11
|
-
};
|
|
12
|
-
}) => Promise<void>) => void;
|
|
13
|
-
type AfterAllFn = (fn: () => Promise<void>) => void;
|
|
14
|
-
declare function trackAgent(agent: Agent): void;
|
|
15
|
-
declare function untrackAgent(agent: Agent): void;
|
|
16
|
-
/**
|
|
17
|
-
* Flush results and dispose agents for the current test. Agents created in
|
|
18
|
-
* this test's scope are disposed after flushing. Shared agents (created in a
|
|
19
|
-
* different scope, e.g. module-level or beforeAll) have their pending results
|
|
20
|
-
* flushed but are kept alive for subsequent tests.
|
|
21
|
-
*/
|
|
22
|
-
declare function flush(task: {
|
|
23
|
-
id: string;
|
|
24
|
-
meta: Pick<import('vitest').TaskMeta, 'pathgrade'>;
|
|
25
|
-
}): Promise<void>;
|
|
26
|
-
declare function onResult(result: RecordedEvalResult, agent: Agent): void;
|
|
27
|
-
/**
|
|
28
|
-
* Dispose any agents still pending at end-of-file. Shared agents (created at
|
|
29
|
-
* module scope or in beforeAll) have a stored task id that won't match any
|
|
30
|
-
* per-test id in afterEach, so afterAll is their only disposal opportunity.
|
|
31
|
-
* Without this, sandboxes leak and `debug: true` folders never get written.
|
|
32
|
-
*/
|
|
33
|
-
declare function flushAll(): Promise<void>;
|
|
34
|
-
declare function reset(): void;
|
|
35
|
-
declare function install(afterEach: AfterEachFn, afterAll?: AfterAllFn): void;
|
|
36
|
-
export declare const lifecycle: {
|
|
37
|
-
trackAgent: typeof trackAgent;
|
|
38
|
-
untrackAgent: typeof untrackAgent;
|
|
39
|
-
flush: typeof flush;
|
|
40
|
-
flushAll: typeof flushAll;
|
|
41
|
-
onResult: typeof onResult;
|
|
42
|
-
reset: typeof reset;
|
|
43
|
-
install: typeof install;
|
|
44
|
-
};
|
|
45
|
-
export {};
|
|
1
|
+
export { lifecycle } from '../adapters/vitest/lifecycle.js';
|
package/dist/plugin/lifecycle.js
CHANGED
|
@@ -1,146 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { buildDiagnosticsReport } from '../reporters/diagnostics.js';
|
|
3
|
-
// Agent tracking: each test creates its own agent(s) and calls evaluate() on them.
|
|
4
|
-
// Results are keyed by agent reference, which is unique per test.
|
|
5
|
-
const pendingAgents = new Set();
|
|
6
|
-
const agentTaskIds = new WeakMap();
|
|
7
|
-
const agentResults = new WeakMap();
|
|
8
|
-
function currentTaskId() {
|
|
9
|
-
try {
|
|
10
|
-
return globalThis.__vitest_worker__?.current?.id ?? '';
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
return '';
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function trackAgent(agent) {
|
|
17
|
-
pendingAgents.add(agent);
|
|
18
|
-
const taskId = currentTaskId();
|
|
19
|
-
if (taskId)
|
|
20
|
-
agentTaskIds.set(agent, taskId);
|
|
21
|
-
}
|
|
22
|
-
function untrackAgent(agent) {
|
|
23
|
-
pendingAgents.delete(agent);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Flush results and dispose agents for the current test. Agents created in
|
|
27
|
-
* this test's scope are disposed after flushing. Shared agents (created in a
|
|
28
|
-
* different scope, e.g. module-level or beforeAll) have their pending results
|
|
29
|
-
* flushed but are kept alive for subsequent tests.
|
|
30
|
-
*/
|
|
31
|
-
async function flush(task) {
|
|
32
|
-
const results = [];
|
|
33
|
-
const toDispose = [];
|
|
34
|
-
for (const agent of pendingAgents) {
|
|
35
|
-
const storedId = agentTaskIds.get(agent);
|
|
36
|
-
const belongsToThisTest = !storedId || storedId === task.id;
|
|
37
|
-
const meta = agentResults.get(agent);
|
|
38
|
-
if (meta && meta.length > 0) {
|
|
39
|
-
// Always flush pending results, even for shared agents
|
|
40
|
-
results.push(...meta);
|
|
41
|
-
agentResults.delete(agent);
|
|
42
|
-
}
|
|
43
|
-
if (belongsToThisTest) {
|
|
44
|
-
if (!meta || meta.length === 0) {
|
|
45
|
-
// evaluate() was never called — synthesize a trial from agent data
|
|
46
|
-
// so the reporter still surfaces token usage and command counts.
|
|
47
|
-
const synthTrial = synthesizeTrialFromAgent(agent);
|
|
48
|
-
if (synthTrial) {
|
|
49
|
-
results.push(synthTrial);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
pendingAgents.delete(agent);
|
|
53
|
-
toDispose.push(agent);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (results.length > 0) {
|
|
57
|
-
task.meta.pathgrade = results;
|
|
58
|
-
}
|
|
59
|
-
await Promise.all(toDispose.map((a) => a.dispose().catch(() => { })));
|
|
60
|
-
}
|
|
61
|
-
function onResult(result, agent) {
|
|
62
|
-
if (!agentResults.has(agent)) {
|
|
63
|
-
agentResults.set(agent, []);
|
|
64
|
-
}
|
|
65
|
-
const conversationEnd = [...agent.log].reverse().find((entry) => entry.type === 'conversation_end');
|
|
66
|
-
const completionReason = conversationEnd?.completion_reason ?? (agent.log.some((entry) => entry.type === 'agent_result') ? 'completed' : undefined);
|
|
67
|
-
agentResults.get(agent).push({
|
|
68
|
-
score: result.score,
|
|
69
|
-
scorers: result.scorers,
|
|
70
|
-
trial: result.trial,
|
|
71
|
-
diagnostics: buildDiagnosticsReport({
|
|
72
|
-
completionReason,
|
|
73
|
-
completionDetail: conversationEnd?.completion_detail,
|
|
74
|
-
turnDetails: conversationEnd?.turn_details,
|
|
75
|
-
reactionsFired: conversationEnd?.reactions_fired,
|
|
76
|
-
score: result.score,
|
|
77
|
-
scorers: result.scorers,
|
|
78
|
-
log: agent.log,
|
|
79
|
-
}),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* When evaluate() is never called, synthesize a minimal trial from the agent's
|
|
84
|
-
* log so the reporter still surfaces command count and token usage.
|
|
85
|
-
*/
|
|
86
|
-
function synthesizeTrialFromAgent(agent) {
|
|
87
|
-
// Only synthesize if the agent actually ran (has log entries)
|
|
88
|
-
if (agent.log.length === 0)
|
|
89
|
-
return null;
|
|
90
|
-
const nCommands = agent.log.filter((e) => e.type === 'command').length;
|
|
91
|
-
const tokenUsage = agent.llm.tokenUsage;
|
|
92
|
-
const conversationEnd = [...agent.log].reverse().find((entry) => entry.type === 'conversation_end');
|
|
93
|
-
const completionReason = conversationEnd?.completion_reason ?? (agent.log.some((entry) => entry.type === 'agent_result') ? 'completed' : undefined);
|
|
94
|
-
return {
|
|
95
|
-
score: 1, // Will be overridden by vitest pass/fail in the reporter
|
|
96
|
-
scorers: [],
|
|
97
|
-
trial: {
|
|
98
|
-
trial_id: 0,
|
|
99
|
-
reward: 1,
|
|
100
|
-
scorer_results: [],
|
|
101
|
-
duration_ms: 0,
|
|
102
|
-
n_commands: nCommands,
|
|
103
|
-
input_tokens: tokenUsage?.inputTokens ?? 0,
|
|
104
|
-
output_tokens: tokenUsage?.outputTokens ?? 0,
|
|
105
|
-
session_log: [...agent.log],
|
|
106
|
-
},
|
|
107
|
-
diagnostics: buildDiagnosticsReport({
|
|
108
|
-
completionReason,
|
|
109
|
-
completionDetail: conversationEnd?.completion_detail,
|
|
110
|
-
turnDetails: conversationEnd?.turn_details,
|
|
111
|
-
reactionsFired: conversationEnd?.reactions_fired,
|
|
112
|
-
score: 1,
|
|
113
|
-
scorers: [],
|
|
114
|
-
log: agent.log,
|
|
115
|
-
}),
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Dispose any agents still pending at end-of-file. Shared agents (created at
|
|
120
|
-
* module scope or in beforeAll) have a stored task id that won't match any
|
|
121
|
-
* per-test id in afterEach, so afterAll is their only disposal opportunity.
|
|
122
|
-
* Without this, sandboxes leak and `debug: true` folders never get written.
|
|
123
|
-
*/
|
|
124
|
-
async function flushAll() {
|
|
125
|
-
const toDispose = [...pendingAgents];
|
|
126
|
-
pendingAgents.clear();
|
|
127
|
-
await Promise.all(toDispose.map((a) => a.dispose().catch(() => { })));
|
|
128
|
-
}
|
|
129
|
-
function reset() {
|
|
130
|
-
pendingAgents.clear();
|
|
131
|
-
}
|
|
132
|
-
function install(afterEach, afterAll) {
|
|
133
|
-
setRuntime({ onResult });
|
|
134
|
-
afterEach(async ({ task }) => flush(task));
|
|
135
|
-
if (afterAll)
|
|
136
|
-
afterAll(async () => flushAll());
|
|
137
|
-
}
|
|
138
|
-
export const lifecycle = {
|
|
139
|
-
trackAgent,
|
|
140
|
-
untrackAgent,
|
|
141
|
-
flush,
|
|
142
|
-
flushAll,
|
|
143
|
-
onResult,
|
|
144
|
-
reset,
|
|
145
|
-
install,
|
|
146
|
-
};
|
|
1
|
+
export { lifecycle } from '../adapters/vitest/lifecycle.js';
|
|
@@ -1,37 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import type { PathgradePluginOptions } from '../sdk/types.js';
|
|
3
|
-
/**
|
|
4
|
-
* Custom vitest reporter that layers pathgrade aggregate statistics
|
|
5
|
-
* on top of vitest's default output.
|
|
6
|
-
*/
|
|
7
|
-
export declare class PathgradeReporter implements Reporter {
|
|
8
|
-
private opts;
|
|
9
|
-
constructor(opts?: PathgradePluginOptions);
|
|
10
|
-
onTestRunEnd(testModules: ReadonlyArray<TestModule>): Promise<void>;
|
|
11
|
-
private collectGroups;
|
|
12
|
-
/**
|
|
13
|
-
* Group key is the parent suite's full name (file + describe hierarchy).
|
|
14
|
-
* Tests without a describe wrapper are grouped by module path.
|
|
15
|
-
*/
|
|
16
|
-
private getGroupKey;
|
|
17
|
-
private toTestEntry;
|
|
18
|
-
private isReportableEntry;
|
|
19
|
-
private printCliSummary;
|
|
20
|
-
private writeJsonResults;
|
|
21
|
-
private buildEvalReport;
|
|
22
|
-
private toTrialResult;
|
|
23
|
-
private slug;
|
|
24
|
-
private openBrowserViewer;
|
|
25
|
-
private computePassRate;
|
|
26
|
-
/**
|
|
27
|
-
* pass@k = 1 - (1 - p)^k
|
|
28
|
-
* Probability of at least 1 success in k trials.
|
|
29
|
-
*/
|
|
30
|
-
private computePassAtK;
|
|
31
|
-
/**
|
|
32
|
-
* pass^k = p^k
|
|
33
|
-
* Probability of all k trials succeeding.
|
|
34
|
-
*/
|
|
35
|
-
private computePassPowK;
|
|
36
|
-
private computeAvgDuration;
|
|
37
|
-
}
|
|
1
|
+
export { PathgradeReporter } from '../adapters/vitest/reporter.js';
|