@wix/pathgrade 0.36.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/LICENSE +24 -0
- package/README.md +508 -0
- 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 +73 -36
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* Parse `pathgrade run` arguments.
|
|
3
3
|
*
|
|
4
4
|
* Recognized pathgrade-specific flags are consumed here; everything else
|
|
5
|
-
* passes through to
|
|
6
|
-
* args are forwarded verbatim
|
|
5
|
+
* passes through to the selected runner. After a literal `--` separator,
|
|
6
|
+
* all remaining args are forwarded verbatim.
|
|
7
7
|
*/
|
|
8
8
|
export interface PathgradeRunArgs {
|
|
9
|
-
|
|
9
|
+
runnerArgs: string[];
|
|
10
|
+
adapterName?: string;
|
|
10
11
|
forceDiagnostics: boolean;
|
|
11
12
|
forceVerbose: boolean;
|
|
12
13
|
changed: boolean;
|
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
* Parse `pathgrade run` arguments.
|
|
3
3
|
*
|
|
4
4
|
* Recognized pathgrade-specific flags are consumed here; everything else
|
|
5
|
-
* passes through to
|
|
6
|
-
* args are forwarded verbatim
|
|
5
|
+
* passes through to the selected runner. After a literal `--` separator,
|
|
6
|
+
* all remaining args are forwarded verbatim.
|
|
7
7
|
*/
|
|
8
8
|
export function parsePathgradeRunArgs(args) {
|
|
9
|
-
const
|
|
9
|
+
const runnerArgs = [];
|
|
10
10
|
let forceDiagnostics = false;
|
|
11
11
|
let forceVerbose = false;
|
|
12
12
|
let changed = false;
|
|
13
13
|
let quiet = false;
|
|
14
14
|
let since;
|
|
15
15
|
let changedFilesPath;
|
|
16
|
+
let adapterName;
|
|
16
17
|
let passthrough = false;
|
|
17
18
|
for (const arg of args) {
|
|
18
19
|
if (passthrough) {
|
|
19
|
-
|
|
20
|
+
runnerArgs.push(arg);
|
|
20
21
|
continue;
|
|
21
22
|
}
|
|
22
23
|
if (arg === '--') {
|
|
@@ -39,6 +40,10 @@ export function parsePathgradeRunArgs(args) {
|
|
|
39
40
|
quiet = true;
|
|
40
41
|
continue;
|
|
41
42
|
}
|
|
43
|
+
if (arg.startsWith('--adapter=')) {
|
|
44
|
+
adapterName = arg.slice('--adapter='.length);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
42
47
|
if (arg.startsWith('--since=')) {
|
|
43
48
|
since = arg.slice('--since='.length);
|
|
44
49
|
continue;
|
|
@@ -47,7 +52,7 @@ export function parsePathgradeRunArgs(args) {
|
|
|
47
52
|
changedFilesPath = arg.slice('--changed-files='.length);
|
|
48
53
|
continue;
|
|
49
54
|
}
|
|
50
|
-
|
|
55
|
+
runnerArgs.push(arg);
|
|
51
56
|
}
|
|
52
57
|
const warnings = [];
|
|
53
58
|
if (!changed && since !== undefined) {
|
|
@@ -57,11 +62,12 @@ export function parsePathgradeRunArgs(args) {
|
|
|
57
62
|
warnings.push('--changed-files has no effect without --changed; the flag is being ignored.');
|
|
58
63
|
}
|
|
59
64
|
const base = {
|
|
60
|
-
|
|
65
|
+
runnerArgs,
|
|
61
66
|
forceDiagnostics,
|
|
62
67
|
forceVerbose,
|
|
63
68
|
changed,
|
|
64
69
|
quiet,
|
|
70
|
+
adapterName,
|
|
65
71
|
since,
|
|
66
72
|
changedFilesPath,
|
|
67
73
|
};
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
* Git-resolution failures exit non-zero. The selection pipeline is the
|
|
13
13
|
* only producer of the file list here.
|
|
14
14
|
*/
|
|
15
|
+
import type { RunnerInvocationAdapter } from '../runners/invocation.js';
|
|
16
|
+
import type { SpawnVitest } from '../runners/vitest-invocation.js';
|
|
15
17
|
import type { PathgradeRunArgs } from './run-args.js';
|
|
16
|
-
export
|
|
17
|
-
argv: string[];
|
|
18
|
-
}
|
|
19
|
-
export type SpawnVitest = (req: SpawnVitestRequest) => Promise<number> | number;
|
|
18
|
+
export type { SpawnVitest };
|
|
20
19
|
export interface RunChangedOptions {
|
|
21
20
|
cwd: string;
|
|
22
21
|
parsed: PathgradeRunArgs;
|
|
23
22
|
/** Override for tests; default spawns `npx vitest` inheriting stdio. */
|
|
24
23
|
spawnVitest?: SpawnVitest;
|
|
24
|
+
runnerInvocation?: RunnerInvocationAdapter;
|
|
25
25
|
}
|
|
26
26
|
export declare function runChanged(opts: RunChangedOptions): Promise<number>;
|
|
@@ -13,15 +13,42 @@
|
|
|
13
13
|
* only producer of the file list here.
|
|
14
14
|
*/
|
|
15
15
|
import * as fs from 'fs';
|
|
16
|
-
import picomatch from 'picomatch';
|
|
17
16
|
import { selectAffected } from '../affected/select.js';
|
|
18
17
|
import { resolveBaseRef, computeChangedFiles } from '../affected/git.js';
|
|
19
|
-
import { loadAffectedConfig } from '../affected/config.js';
|
|
20
18
|
import { writeSidecar } from '../affected/sidecar.js';
|
|
21
|
-
import {
|
|
19
|
+
import { discoverPathgradeEvalFiles } from '../evals/discovery.js';
|
|
20
|
+
import { resolvePathgradeConfig } from '../config/pathgrade.js';
|
|
21
|
+
import { loadRunnerInvocationAdapter } from '../runners/adapter-loader.js';
|
|
22
22
|
export async function runChanged(opts) {
|
|
23
23
|
const { cwd, parsed } = opts;
|
|
24
|
-
const
|
|
24
|
+
const runnerEnv = {
|
|
25
|
+
...process.env,
|
|
26
|
+
...(parsed.forceDiagnostics ? { PATHGRADE_DIAGNOSTICS: '1' } : {}),
|
|
27
|
+
...(parsed.forceVerbose ? { PATHGRADE_VERBOSE: '1' } : {}),
|
|
28
|
+
};
|
|
29
|
+
const configPath = findVitestConfigArg(parsed.runnerArgs);
|
|
30
|
+
let config;
|
|
31
|
+
let runnerInvocation;
|
|
32
|
+
try {
|
|
33
|
+
config = await resolvePathgradeConfig({
|
|
34
|
+
cwd,
|
|
35
|
+
legacyVitestConfigPath: configPath,
|
|
36
|
+
warn: w => {
|
|
37
|
+
if (!parsed.quiet)
|
|
38
|
+
process.stderr.write(`${w}\n`);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
runnerInvocation = opts.runnerInvocation ?? await loadRunnerInvocationAdapter({
|
|
42
|
+
adapterName: parsed.adapterName ?? config.runner.adapter,
|
|
43
|
+
cwd,
|
|
44
|
+
config,
|
|
45
|
+
spawnVitest: opts.spawnVitest,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
process.stderr.write(`${errMsg(err)}\n`);
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
25
52
|
// 1. Change-set
|
|
26
53
|
let baseRef;
|
|
27
54
|
let changedFiles;
|
|
@@ -53,22 +80,11 @@ export async function runChanged(opts) {
|
|
|
53
80
|
baseRefLine = `pathgrade: base = ${baseRef} (merge-base with HEAD)`;
|
|
54
81
|
}
|
|
55
82
|
// 2. Selection
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
config
|
|
60
|
-
|
|
61
|
-
onWarning: w => {
|
|
62
|
-
if (!parsed.quiet)
|
|
63
|
-
process.stderr.write(`${w}\n`);
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
process.stderr.write(`${errMsg(err)}\n`);
|
|
69
|
-
return 1;
|
|
70
|
-
}
|
|
71
|
-
const evalFiles = filterEvalFilesForConfig(discoverEvalFiles(cwd), config);
|
|
83
|
+
const evalFiles = discoverPathgradeEvalFiles({
|
|
84
|
+
cwd,
|
|
85
|
+
include: config.evals.include,
|
|
86
|
+
exclude: config.evals.exclude,
|
|
87
|
+
});
|
|
72
88
|
let result;
|
|
73
89
|
try {
|
|
74
90
|
result = selectAffected({
|
|
@@ -76,7 +92,7 @@ export async function runChanged(opts) {
|
|
|
76
92
|
changedFiles,
|
|
77
93
|
repoRoot: cwd,
|
|
78
94
|
baseRef,
|
|
79
|
-
global: config.global,
|
|
95
|
+
global: config.affected.global,
|
|
80
96
|
});
|
|
81
97
|
}
|
|
82
98
|
catch (err) {
|
|
@@ -103,16 +119,16 @@ export async function runChanged(opts) {
|
|
|
103
119
|
return 0;
|
|
104
120
|
}
|
|
105
121
|
const selectedFiles = result.selected.map(s => s.file);
|
|
106
|
-
|
|
107
|
-
process.stderr.write('pathgrade run: --passWithNoTests cannot be used with pathgrade run --changed. ' +
|
|
108
|
-
'The command already exits 0 when no evals are selected; if selected evals resolve to no Vitest files, CI must fail.\n');
|
|
109
|
-
return 1;
|
|
110
|
-
}
|
|
111
|
-
const argv = ['run', ...selectedFiles, ...parsed.vitestArgs];
|
|
122
|
+
const runnerArgs = [...config.runner.args, ...parsed.runnerArgs];
|
|
112
123
|
if (!parsed.quiet) {
|
|
113
|
-
process.stderr.write(`→
|
|
124
|
+
process.stderr.write(`→ ${runnerInvocation.name} run ${selectedFiles.join(' ')}\n`);
|
|
114
125
|
}
|
|
115
|
-
return await
|
|
126
|
+
return await runnerInvocation.run({
|
|
127
|
+
cwd,
|
|
128
|
+
runnerArgs,
|
|
129
|
+
selectedFiles,
|
|
130
|
+
env: runnerEnv,
|
|
131
|
+
});
|
|
116
132
|
}
|
|
117
133
|
function printRunStartSummary(input) {
|
|
118
134
|
const { baseRefLine, totalEvals, changedCount, result } = input;
|
|
@@ -141,15 +157,6 @@ function readChangedFilesList(filePath) {
|
|
|
141
157
|
function errMsg(err) {
|
|
142
158
|
return err instanceof Error ? err.message : String(err);
|
|
143
159
|
}
|
|
144
|
-
function hasPassWithNoTests(args) {
|
|
145
|
-
return args.some(arg => {
|
|
146
|
-
if (arg === '--passWithNoTests')
|
|
147
|
-
return true;
|
|
148
|
-
if (!arg.startsWith('--passWithNoTests='))
|
|
149
|
-
return false;
|
|
150
|
-
return arg.slice('--passWithNoTests='.length).toLowerCase() !== 'false';
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
160
|
function findVitestConfigArg(args) {
|
|
154
161
|
for (let i = 0; i < args.length; i++) {
|
|
155
162
|
const arg = args[i];
|
|
@@ -162,23 +169,3 @@ function findVitestConfigArg(args) {
|
|
|
162
169
|
}
|
|
163
170
|
return undefined;
|
|
164
171
|
}
|
|
165
|
-
function filterEvalFilesForConfig(evalFiles, config) {
|
|
166
|
-
if (!config.include && !config.exclude)
|
|
167
|
-
return evalFiles;
|
|
168
|
-
const includeMatchers = config.include?.map(g => picomatch(g, { dot: true }));
|
|
169
|
-
const excludeMatchers = config.exclude?.map(g => picomatch(g, { dot: true })) ?? [];
|
|
170
|
-
return evalFiles.filter(file => {
|
|
171
|
-
const included = includeMatchers ? includeMatchers.some(m => m(file)) : true;
|
|
172
|
-
return included && !excludeMatchers.some(m => m(file));
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
async function defaultSpawnVitest(req) {
|
|
176
|
-
const { spawn } = await import('child_process');
|
|
177
|
-
return await new Promise(resolve => {
|
|
178
|
-
const child = spawn('npx', ['vitest', ...req.argv], {
|
|
179
|
-
stdio: 'inherit',
|
|
180
|
-
shell: true,
|
|
181
|
-
});
|
|
182
|
-
child.on('close', code => resolve(code ?? 0));
|
|
183
|
-
});
|
|
184
|
-
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface PathgradeConfig {
|
|
2
|
+
runner?: {
|
|
3
|
+
adapter?: string;
|
|
4
|
+
args?: string[];
|
|
5
|
+
};
|
|
6
|
+
evals?: {
|
|
7
|
+
include?: string[];
|
|
8
|
+
exclude?: string[];
|
|
9
|
+
};
|
|
10
|
+
affected?: {
|
|
11
|
+
global?: string[];
|
|
12
|
+
};
|
|
13
|
+
reporter?: 'cli' | 'browser' | 'json';
|
|
14
|
+
diagnostics?: boolean;
|
|
15
|
+
verbose?: boolean;
|
|
16
|
+
ci?: {
|
|
17
|
+
threshold?: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface ResolvedPathgradeConfig {
|
|
21
|
+
runner: {
|
|
22
|
+
adapter: string;
|
|
23
|
+
args: string[];
|
|
24
|
+
};
|
|
25
|
+
evals: {
|
|
26
|
+
include: string[];
|
|
27
|
+
exclude: string[];
|
|
28
|
+
};
|
|
29
|
+
affected: {
|
|
30
|
+
global: string[];
|
|
31
|
+
};
|
|
32
|
+
reporter?: 'cli' | 'browser' | 'json';
|
|
33
|
+
diagnostics: boolean;
|
|
34
|
+
verbose: boolean;
|
|
35
|
+
ci: {
|
|
36
|
+
threshold?: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export declare const DEFAULT_EVAL_INCLUDE: string[];
|
|
40
|
+
export declare const DEFAULT_EVAL_EXCLUDE: string[];
|
|
41
|
+
export declare function defaultPathgradeConfig(): ResolvedPathgradeConfig;
|
|
42
|
+
export declare function resolvePathgradeConfig(input: {
|
|
43
|
+
cwd: string;
|
|
44
|
+
cli?: PathgradeConfig;
|
|
45
|
+
configPath?: string;
|
|
46
|
+
legacyVitestConfigPath?: string;
|
|
47
|
+
warn?: (message: string) => void;
|
|
48
|
+
}): Promise<ResolvedPathgradeConfig>;
|
|
@@ -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';
|