@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.
Files changed (117) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +508 -0
  3. package/dist/adapter-kit/index.d.ts +17 -0
  4. package/dist/adapter-kit/index.js +14 -0
  5. package/dist/adapters/jest/index.d.ts +3 -0
  6. package/dist/adapters/jest/index.js +3 -0
  7. package/dist/adapters/jest/invocation-adapter.d.ts +15 -0
  8. package/dist/adapters/jest/invocation-adapter.js +115 -0
  9. package/dist/adapters/jest/lifecycle.d.ts +21 -0
  10. package/dist/adapters/jest/lifecycle.js +62 -0
  11. package/dist/adapters/jest/metadata.d.ts +6 -0
  12. package/dist/adapters/jest/metadata.js +35 -0
  13. package/dist/adapters/jest/reporter.cjs +10 -0
  14. package/dist/adapters/jest/reporter.d.ts +5 -0
  15. package/dist/adapters/jest/reporter.js +67 -0
  16. package/dist/adapters/jest/results.d.ts +31 -0
  17. package/dist/adapters/jest/results.js +144 -0
  18. package/dist/adapters/jest/runner-adapter.d.ts +9 -0
  19. package/dist/adapters/jest/runner-adapter.js +49 -0
  20. package/dist/adapters/jest/setup.d.ts +1 -0
  21. package/dist/adapters/jest/setup.js +10 -0
  22. package/dist/adapters/node-test/index.d.ts +8 -0
  23. package/dist/adapters/node-test/index.js +106 -0
  24. package/dist/adapters/node-test/invocation-adapter.d.ts +5 -0
  25. package/dist/adapters/node-test/invocation-adapter.js +47 -0
  26. package/dist/adapters/node-test/runner-adapter.d.ts +2 -0
  27. package/dist/adapters/node-test/runner-adapter.js +116 -0
  28. package/dist/adapters/vitest/index.d.ts +9 -0
  29. package/dist/adapters/vitest/index.js +82 -0
  30. package/dist/adapters/vitest/lifecycle.d.ts +24 -0
  31. package/dist/adapters/vitest/lifecycle.js +41 -0
  32. package/dist/adapters/vitest/reporter.d.ts +12 -0
  33. package/dist/adapters/vitest/reporter.js +75 -0
  34. package/dist/adapters/vitest/setup.d.ts +1 -0
  35. package/dist/adapters/vitest/setup.js +3 -0
  36. package/dist/agents/codex-app-server/agent.js +5 -1
  37. package/dist/agents/codex-app-server/protocol/index.js +3 -3
  38. package/dist/agents/codex-app-server/turn-completion.d.ts +1 -0
  39. package/dist/agents/codex-app-server/turn-completion.js +9 -0
  40. package/dist/commands/affected.js +22 -15
  41. package/dist/commands/run-args.d.ts +4 -3
  42. package/dist/commands/run-args.js +12 -6
  43. package/dist/commands/run-changed.d.ts +4 -4
  44. package/dist/commands/run-changed.js +45 -58
  45. package/dist/config/pathgrade.d.ts +48 -0
  46. package/dist/config/pathgrade.js +204 -0
  47. package/dist/pathgrade.d.ts +2 -2
  48. package/dist/pathgrade.js +35 -34
  49. package/dist/plugin/index.d.ts +2 -9
  50. package/dist/plugin/index.js +1 -81
  51. package/dist/plugin/lifecycle.d.ts +1 -45
  52. package/dist/plugin/lifecycle.js +1 -146
  53. package/dist/plugin/reporter.d.ts +1 -37
  54. package/dist/plugin/reporter.js +1 -280
  55. package/dist/plugin/setup.d.ts +1 -1
  56. package/dist/plugin/setup.js +1 -3
  57. package/dist/providers/copy-filter.d.ts +5 -0
  58. package/dist/providers/copy-filter.js +9 -0
  59. package/dist/providers/sandbox.js +2 -2
  60. package/dist/providers/workspace.d.ts +6 -0
  61. package/dist/providers/workspace.js +34 -14
  62. package/dist/reporters/diagnostics.d.ts +2 -40
  63. package/dist/reporters/diagnostics.js +1 -80
  64. package/dist/reporters/report-summary.d.ts +6 -0
  65. package/dist/reporters/report-summary.js +29 -0
  66. package/dist/reporting/artifacts.d.ts +2 -0
  67. package/dist/reporting/artifacts.js +20 -0
  68. package/dist/reporting/core.d.ts +2 -0
  69. package/dist/reporting/core.js +173 -0
  70. package/dist/reporting/types.d.ts +57 -0
  71. package/dist/reporting/types.js +1 -0
  72. package/dist/reporting/vitest-edge.d.ts +1 -0
  73. package/dist/reporting/vitest-edge.js +1 -0
  74. package/dist/runners/adapter-loader.d.ts +17 -0
  75. package/dist/runners/adapter-loader.js +67 -0
  76. package/dist/runners/adapter.d.ts +65 -0
  77. package/dist/runners/adapter.js +1 -0
  78. package/dist/runners/invocation.d.ts +10 -0
  79. package/dist/runners/invocation.js +1 -0
  80. package/dist/runners/lifecycle-hooks.d.ts +2 -0
  81. package/dist/runners/lifecycle-hooks.js +18 -0
  82. package/dist/runners/model-builders.d.ts +4 -0
  83. package/dist/runners/model-builders.js +67 -0
  84. package/dist/runners/model-validation.d.ts +12 -0
  85. package/dist/runners/model-validation.js +213 -0
  86. package/dist/runners/model.d.ts +117 -0
  87. package/dist/runners/model.js +1 -0
  88. package/dist/runners/orchestrator.d.ts +30 -0
  89. package/dist/runners/orchestrator.js +74 -0
  90. package/dist/runners/report-projection.d.ts +6 -0
  91. package/dist/runners/report-projection.js +62 -0
  92. package/dist/runners/selection.d.ts +6 -0
  93. package/dist/runners/selection.js +10 -0
  94. package/dist/runners/vitest-adapter.d.ts +8 -0
  95. package/dist/runners/vitest-adapter.js +108 -0
  96. package/dist/runners/vitest-invocation.d.ts +10 -0
  97. package/dist/runners/vitest-invocation.js +44 -0
  98. package/dist/runners/vitest-lifecycle.d.ts +43 -0
  99. package/dist/runners/vitest-lifecycle.js +150 -0
  100. package/dist/sdk/agent.js +15 -16
  101. package/dist/sdk/case-context.d.ts +20 -0
  102. package/dist/sdk/case-context.js +31 -0
  103. package/dist/sdk/diagnostics.d.ts +40 -0
  104. package/dist/sdk/diagnostics.js +80 -0
  105. package/dist/sdk/eval-runtime.d.ts +4 -0
  106. package/dist/sdk/eval-runtime.js +11 -1
  107. package/dist/sdk/evaluate.js +2 -1
  108. package/dist/sdk/index.d.ts +2 -0
  109. package/dist/sdk/index.js +1 -0
  110. package/dist/sdk/lifecycle.d.ts +40 -0
  111. package/dist/sdk/lifecycle.js +170 -0
  112. package/dist/sdk/result-capture.d.ts +18 -0
  113. package/dist/sdk/result-capture.js +49 -0
  114. package/dist/sdk/types.d.ts +1 -1
  115. package/dist/types.d.ts +2 -2
  116. package/dist/utils/llm-providers/anthropic.js +9 -2
  117. package/package.json +73 -36
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Nadav Lachish
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ NOTICE: Pathgrade was originally forked from skillgrade by Minko Gechev,
24
+ which is MIT licensed.
package/README.md ADDED
@@ -0,0 +1,508 @@
1
+ # Pathgrade
2
+
3
+ **Evaluate AI coding agents with Vitest or Jest.** Write evals as normal `.eval.ts` files, run Claude Code, Codex, or Cursor in isolated sandboxes, and score the result with deterministic checks, rubric judges, and tool-usage assertions.
4
+
5
+ ## Why Pathgrade?
6
+
7
+ - Write evals in plain TypeScript with Vitest or Jest
8
+ - Run each trial in an isolated workspace and HOME directory
9
+ - Seed trials from fixtures, real skills, or mocked MCP servers
10
+ - Score both final artifacts and the workflow that produced them
11
+ - Debug long conversations with preserved workspaces and run snapshots
12
+ - Use the same evals locally and in CI
13
+
14
+ ## Quick Start
15
+
16
+ **Prerequisites**: Node.js 20.11+, Vitest 4+ or Jest 30+, and at least one configured agent runtime. Claude uses the bundled `@anthropic-ai/claude-agent-sdk` binary by default; Codex requires the `codex` CLI; Cursor requires the `cursor-agent` CLI.
17
+
18
+ ```bash
19
+ yarn add -D @wix/pathgrade
20
+ ```
21
+
22
+ For Jest projects, install Jest too:
23
+
24
+ ```bash
25
+ yarn add -D @wix/pathgrade jest
26
+ ```
27
+
28
+ ### Authentication
29
+
30
+ By default, Pathgrade tries to reuse the agent CLI's native auth before falling back to explicit environment variables.
31
+
32
+ - **Claude**
33
+ - macOS: reuses Claude Code OAuth from Keychain
34
+ - other platforms: forwards `ANTHROPIC_API_KEY` when present
35
+ - **Codex**
36
+ - forwards `OPENAI_API_KEY` when present
37
+ - or runs `codex login --with-api-key` inside the sandbox when an API key is present
38
+ - `codex exec` can reuse cached `~/.codex/auth.json` when no key is available; `app-server` may reject cached ChatGPT-token refreshes, so prefer `OPENAI_API_KEY` for the default transport
39
+ - **Cursor**
40
+ - forwards `CURSOR_API_KEY` when set
41
+ - macOS: reuses `cursor-agent login` OAuth tokens from the login Keychain
42
+ - surfaces a clear error when neither is available (run `cursor-agent login` or set `CURSOR_API_KEY`)
43
+
44
+ If you set `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`, or `CURSOR_API_BASE_URL`, set the matching API key too.
45
+
46
+ Override credentials per test with `env`:
47
+
48
+ ```typescript
49
+ const agent = await createAgent({
50
+ agent: 'claude',
51
+ env: {
52
+ ANTHROPIC_API_KEY: process.env.MY_ANTHROPIC_KEY!,
53
+ },
54
+ });
55
+ ```
56
+
57
+ ### Transport (Codex only)
58
+
59
+ Codex supports two transports and Pathgrade defaults to `app-server`:
60
+
61
+ - `app-server` (default) — uses `codex app-server` and keeps native thread state. Required for `AskUserReaction` handshakes (`request_user_input` reaches the model). Prefer `OPENAI_API_KEY`; cached ChatGPT auth can fail if the app-server asks Pathgrade to refresh tokens.
62
+ - `exec` — uses `codex exec` and re-injects the transcript every turn. Kept for stateless CI matrices that don't need the handshake.
63
+
64
+ Precedence: `createAgent({ transport })` > `PATHGRADE_CODEX_TRANSPORT` env > default (`app-server`). An invalid env value throws at `createAgent` time.
65
+
66
+ ```typescript
67
+ const agent = await createAgent({
68
+ agent: 'codex',
69
+ transport: 'exec', // opt out of app-server
70
+ });
71
+ ```
72
+
73
+ If `transport: 'exec'` is resolved and any `AskUserReaction` is present in `ConverseOptions.reactions`, the conversation fails fast before turn 1 — the handshake cannot fire under `exec`. Set `allowUnreachableReactions: true` on `runConversation` to silence the guard.
74
+
75
+ Migrating from `exec` to `app-server`:
76
+
77
+ - Export `OPENAI_API_KEY`, or set `transport: 'exec'` / `PATHGRADE_CODEX_TRANSPORT=exec` to stay on the old transport and its cached-auth behavior.
78
+ - The `noninteractive-user-question` runtime policy no longer attaches under `app-server`. Snapshots that captured model output influenced by that policy text may need re-recording.
79
+ - `MAX_TURN_RETRIES` does not apply under `app-server` — a crashed turn ends the conversation with `completionReason: 'agent_crashed'`.
80
+
81
+ ### Plugin Setup
82
+
83
+ Create a `vitest.config.ts` with the built-in Vitest adapter plugin:
84
+
85
+ ```typescript
86
+ import { defineConfig } from 'vitest/config';
87
+ import { pathgrade } from '@wix/pathgrade/adapters/vitest';
88
+
89
+ export default defineConfig({
90
+ plugins: [pathgrade({ timeout: 120 })],
91
+ });
92
+ ```
93
+
94
+ The plugin registers the setup hooks Pathgrade needs, wires in the reporter, and automatically cleans up agent workspaces after each test.
95
+
96
+ For Pathgrade CLI behavior, use `pathgrade.config.ts`:
97
+
98
+ ```typescript
99
+ export default {
100
+ runner: {
101
+ adapter: 'vitest', // default; use 'jest' for Jest projects
102
+ args: [],
103
+ },
104
+ evals: {
105
+ include: ['**/*.eval.ts'],
106
+ exclude: ['**/fixtures/**'],
107
+ },
108
+ affected: {
109
+ global: ['package.json', 'yarn.lock'],
110
+ },
111
+ };
112
+ ```
113
+
114
+ ### First Eval
115
+
116
+ Write an eval file such as `hello.eval.ts`:
117
+
118
+ ```typescript
119
+ import * as fs from 'fs';
120
+ import * as path from 'path';
121
+ import { describe, it, expect } from 'vitest';
122
+ import { createAgent, check, evaluate } from '@wix/pathgrade';
123
+
124
+ describe('hello world', () => {
125
+ it('agent creates the requested file', async () => {
126
+ const agent = await createAgent({
127
+ agent: 'claude',
128
+ workspace: path.join(__dirname, 'fixtures'),
129
+ });
130
+
131
+ await agent.prompt('Create a file called hello.txt with the text "Hello, world!"');
132
+
133
+ const result = await evaluate(agent, [
134
+ check('hello.txt exists', ({ workspace }) =>
135
+ fs.existsSync(path.join(workspace, 'hello.txt'))),
136
+ ]);
137
+
138
+ expect(result.score).toBe(1);
139
+ });
140
+ });
141
+ ```
142
+
143
+ Run your evals:
144
+
145
+ ```bash
146
+ npx pathgrade run
147
+ ```
148
+
149
+ `pathgrade run` is the recommended wrapper: it loads `.env`, warns when no auth is configured, and adds Pathgrade-specific flags such as `--changed`, `--diagnostics`, and `--verbose`. Plain `npx vitest run` or direct Jest runs work too if you configure the Pathgrade adapter hooks yourself.
150
+
151
+ ## Core Concepts
152
+
153
+ - **Agent**: the coding agent under test, such as Claude, Codex, or Cursor
154
+ - **Workspace**: an isolated directory where the agent works, optionally seeded from fixtures
155
+ - **Scorer**: a function or judge that evaluates output or behavior
156
+ - **Evaluation**: the aggregated result of one or more scorers, returned as a score from `0.0` to `1.0`
157
+
158
+ ## Scorers
159
+
160
+ Scorers evaluate the agent's output and behavior. `evaluate()` runs all scorers and computes a weighted average between `0.0` and `1.0`.
161
+
162
+ Use `check()` for binary requirements, `score()` for partial credit, `judge()` for rubric-based evaluation, and `toolUsage()` when the workflow matters as much as the final output.
163
+
164
+ ### `check()` - Boolean gate
165
+
166
+ ```typescript
167
+ check('tests-pass', async ({ runCommand }) => {
168
+ const { exitCode } = await runCommand('npm test');
169
+ return exitCode === 0;
170
+ });
171
+ ```
172
+
173
+ ### `score()` - Partial credit
174
+
175
+ ```typescript
176
+ score('coverage', async ({ runCommand }) => {
177
+ const { stdout } = await runCommand('npx coverage-summary');
178
+ return parseFloat(stdout) / 100;
179
+ });
180
+ ```
181
+
182
+ ### `judge()` - Rubric evaluation
183
+
184
+ ```typescript
185
+ judge('workflow-quality', {
186
+ rubric: `Did the agent read the file before editing? (0-0.5)
187
+ Was the fix minimal and correct? (0-0.5)`,
188
+ });
189
+ ```
190
+
191
+ Judge scorers also support:
192
+
193
+ - `retry` for transient judge failures
194
+ - `includeToolEvents` when the rubric should see the tool trace
195
+ - `input` for extra context such as generated file contents or command output
196
+ - `tools` to let the judge LLM read workspace artifacts itself via a bounded tool-use loop (`readFile`, `listDir`, `grep`, `getToolEvents`)
197
+
198
+ Example with artifact-backed input:
199
+
200
+ ```typescript
201
+ judge('output-quality', {
202
+ rubric: 'Is the generated markdown correct and complete?',
203
+ includeToolEvents: true,
204
+ input: async ({ artifacts }) => ({
205
+ 'output.md': await artifacts.read('output.md'),
206
+ }),
207
+ });
208
+ ```
209
+
210
+ Example with tool-using judge (the judge reads the file itself — no pre-computed probe):
211
+
212
+ ```typescript
213
+ judge('spec-structure', {
214
+ rubric: `Read artifacts/spec.md. Score 1.0 if it contains Intent Hierarchy,
215
+ Functional Requirements, and API Surface sections. 0.33 per section.`,
216
+ tools: ['readFile'],
217
+ });
218
+ ```
219
+
220
+ Tool-using judges currently require the Anthropic HTTP provider (`ANTHROPIC_API_KEY`); other providers produce a clean `provider_not_supported` error. See the [User Guide](docs/USER_GUIDE.md#tool-using-judges) for the full tool list, failure codes, and the migration recipe from `input`-helper probes.
221
+
222
+ ### `toolUsage()` - Tool event matching
223
+
224
+ ```typescript
225
+ toolUsage('expected-workflow', [
226
+ { action: 'read_file', min: 1, weight: 0.3 },
227
+ { action: 'edit_file', min: 1, weight: 0.3 },
228
+ { action: 'run_shell', commandContains: 'test', min: 1, weight: 0.4 },
229
+ ]);
230
+ ```
231
+
232
+ ## Conversations
233
+
234
+ Pathgrade currently supports three agent backends: `claude`, `codex`, and `cursor`. Set the backend per test via `createAgent({ agent: 'claude' })`, or omit `agent` and use `PATHGRADE_AGENT` as the process-wide fallback.
235
+
236
+ ### `agent.prompt()` - One shot
237
+
238
+ Send a single instruction and let the agent work to completion:
239
+
240
+ ```typescript
241
+ const agent = await createAgent({ agent: 'claude', workspace: 'fixtures' });
242
+ await agent.prompt('Create a file called hello.txt with the text "Hello, world!"');
243
+ ```
244
+
245
+ ### `startChat()` - Imperative
246
+
247
+ Drive the conversation yourself:
248
+
249
+ ```typescript
250
+ const chat = await agent.startChat('Set up a new TypeScript project.');
251
+ await chat.reply('Use strict mode and add eslint.');
252
+ if (await chat.hasFile('tsconfig.json')) {
253
+ await chat.reply('Now add a build script.');
254
+ }
255
+ chat.end();
256
+ ```
257
+
258
+ ### `runConversation()` - Scripted or persona-driven
259
+
260
+ Drive the loop with reactions:
261
+
262
+ ```typescript
263
+ const result = await agent.runConversation({
264
+ firstMessage: 'I want to create a new feature.',
265
+ maxTurns: 12,
266
+ until: async ({ hasFile }) => await hasFile('project-brief.md'),
267
+ reactions: [
268
+ { when: /goal/i, reply: 'Solve a user pain point' },
269
+ { when: /audience/i, reply: 'Self-Creator' },
270
+ ],
271
+ });
272
+ ```
273
+
274
+ Or let a persona answer on the user's behalf:
275
+
276
+ ```typescript
277
+ const result = await agent.runConversation({
278
+ firstMessage: 'I want to create a new feature.',
279
+ maxTurns: 12,
280
+ until: async ({ hasFile }) => await hasFile('project-brief.md'),
281
+ persona: {
282
+ description: 'A product manager who communicates concisely.',
283
+ facts: ['The feature is for online stores'],
284
+ },
285
+ });
286
+ ```
287
+
288
+ `runConversation()` also supports `stepScorers`, so long conversations can be graded at intermediate milestones instead of only at the end.
289
+
290
+ ## Advanced SDK Features
291
+
292
+ Pathgrade exposes a few useful features that are easy to miss from the basic examples:
293
+
294
+ - `createAgent({ skillDir, workspace })` stages a real skill and a fixture workspace into the sandbox, which is how Pathgrade's skill examples are evaluated.
295
+ - `createAgent({ debug: true })` preserves the final workspace under `pathgrade-debug/<test-name>/`; when you use `runConversation()`, it also writes `run-snapshot.json`.
296
+ - `evaluate.fromSnapshot(snapshotPath, scorers)` re-runs grading against a saved snapshot without re-running the agent.
297
+ - `previewReactions(messages, reactions)` lets you inspect which scripted reactions would fire offline.
298
+ - `conversationWindow` on agents and personas keeps long transcripts bounded with summarization instead of sending the full conversation every turn.
299
+ - `copyIgnore` and `DEFAULT_COPY_IGNORE` let you control what gets copied into the sandbox when seeding from large fixtures or skill directories.
300
+
301
+ See [sdk-showcase](examples/sdk-showcase/) for a single example suite that demonstrates these APIs together.
302
+
303
+ ## MCP Mock Servers
304
+
305
+ Simulate MCP tools when testing Claude, Codex app-server, or Cursor evals:
306
+
307
+ ```typescript
308
+ import { mockMcpServer } from '@wix/pathgrade/mcp-mock';
309
+
310
+ const mock = mockMcpServer({
311
+ name: 'weather',
312
+ tools: [{
313
+ name: 'get_weather',
314
+ description: 'Get weather for a city',
315
+ when: 'weather',
316
+ response: { temp: 72, unit: 'F' },
317
+ }],
318
+ });
319
+
320
+ const agent = await createAgent({ agent: 'claude', mcpMock: mock });
321
+ ```
322
+
323
+ ## CLI
324
+
325
+ ```bash
326
+ pathgrade run [--changed] [--since=<ref>] [--changed-files=<path>] [--adapter=<name|path>] [--diagnostics] [--verbose] [--quiet] [-- runner-args]
327
+ pathgrade init [--force]
328
+ pathgrade validate <file.eval.ts>
329
+ pathgrade validate --affected
330
+ pathgrade analyze [--skill=<name>] [--dir=<path>]
331
+ pathgrade affected [--since=<ref>] [--changed-files=<path>] [--explain] [--json]
332
+ pathgrade preview [browser] [--last=N] [--filter=text]
333
+ pathgrade preview-reactions --snapshot <run-snapshot.json> --reactions <file.ts>
334
+ pathgrade report [--results-path=<path>] [--no-comment] [--comment-id=<id>]
335
+ ```
336
+
337
+ Useful details:
338
+
339
+ - `pathgrade run --changed` computes affected evals first, writes selection metadata to `.pathgrade/selection.json`, and only then launches the selected runner adapter. Vitest is the default adapter; Jest is selected with `runner.adapter: 'jest'` or `--adapter=jest`.
340
+ - `pathgrade preview browser` starts a local viewer on `http://localhost:3847`.
341
+ - `pathgrade report` posts or updates a PR comment in GitHub Actions; locally it prints the markdown report and then the numeric pass rate.
342
+ - `pathgrade validate --affected` is a strict mode for CI: every discovered eval must either live under a `SKILL.md` anchor or export valid `__pathgradeMeta`.
343
+
344
+ Run `pathgrade --help` for the full help text.
345
+
346
+ ## Configuration
347
+
348
+ ```typescript
349
+ // pathgrade.config.ts
350
+ export default {
351
+ runner: {
352
+ adapter: 'vitest',
353
+ args: [],
354
+ },
355
+ evals: {
356
+ include: ['**/*.eval.ts'], // default
357
+ exclude: ['**/fixtures/**'], // replaces the default exclude list if set
358
+ },
359
+ affected: {
360
+ global: ['package.json', 'yarn.lock'],
361
+ },
362
+ ci: { threshold: 0.8 },
363
+ };
364
+ ```
365
+
366
+ Pathgrade reads `pathgrade.config.*` for CLI and affected-selection behavior. `runner.adapter` and `--adapter=<name|path>` select the runner; `--adapter` wins over config. Built-in adapters currently include `vitest`, `jest`, and the narrow `node-test` proof adapter.
367
+
368
+ Third-party runner adapters are supported through `@wix/pathgrade/adapter-kit`. Adapter names resolve as follows:
369
+
370
+ - `vitest`, `jest`, `node-test`: built-in adapters
371
+ - `demo`: package `@wix/pathgrade-adapter-demo`, resolved from the project
372
+ - `@scope/pathgrade-adapter-demo` or another specifier containing `/`: package specifier, resolved from the project
373
+ - `./local-adapter.mjs` or `/abs/local-adapter.mjs`: local adapter module
374
+
375
+ External adapter modules used by `pathgrade run` must export `createPathgradeInvocationAdapter({ config })`, returning a `RunnerInvocationAdapter`. Modules used by lower-level orchestration can also export `createPathgradeAdapter()`, returning a `RunnerAdapter` with `discover`, `invoke`, and `collectNormalizedRunSnapshot`.
376
+
377
+ Vitest runner behavior still belongs in `vitest.config.ts`:
378
+
379
+ ```typescript
380
+ import { pathgrade } from '@wix/pathgrade/adapters/vitest';
381
+
382
+ pathgrade({
383
+ timeout: 300, // seconds, default: 300
384
+ reporter: 'cli', // 'cli' | 'browser' | 'json'
385
+ diagnostics: false, // print full diagnostics for passing evals too
386
+ verbose: false, // stream live per-turn events to stderr while evals run
387
+ });
388
+ ```
389
+
390
+ Jest runner behavior still belongs in `jest.config.*`. Pathgrade injects only the setup and reporter it needs when you use `pathgrade run --adapter=jest`; transforms, test environment, module resolution, and ESM/TypeScript support remain your Jest config's job.
391
+
392
+ For direct Jest runs, configure the same entry points explicitly:
393
+
394
+ ```js
395
+ // jest.config.mjs
396
+ export default {
397
+ setupFilesAfterEnv: ['@wix/pathgrade/adapters/jest/setup'],
398
+ reporters: ['default', '@wix/pathgrade/adapters/jest/reporter'],
399
+ };
400
+ ```
401
+
402
+ Notes:
403
+
404
+ - Legacy `@wix/pathgrade/plugin` and `@wix/pathgrade/plugin/vitest` imports remain as compatibility fallbacks, but new config should use `@wix/pathgrade/adapters/vitest` and `pathgrade.config.*`.
405
+ - `reporter: 'browser'` writes results JSON and opens the viewer automatically after the run.
406
+ - SDK-only consumers can import `@wix/pathgrade` without installing Vitest or Jest. Adapter users need their selected runner available.
407
+
408
+ ## Environment Variables
409
+
410
+ | Variable | Purpose |
411
+ |----------|---------|
412
+ | `ANTHROPIC_API_KEY` | Claude auth and the required key when using `ANTHROPIC_BASE_URL` |
413
+ | `OPENAI_API_KEY` | Codex auth and the required key when using `OPENAI_BASE_URL` |
414
+ | `CURSOR_API_KEY` | Cursor auth and the required key when using `CURSOR_API_BASE_URL` |
415
+ | `ANTHROPIC_BASE_URL` | Custom Anthropic-compatible endpoint |
416
+ | `OPENAI_BASE_URL` | Custom OpenAI-compatible endpoint |
417
+ | `CURSOR_API_BASE_URL` | Custom Cursor-compatible endpoint |
418
+ | `PATHGRADE_AGENT` | Fallback agent for all tests (`claude`, `codex`, or `cursor`). `createAgent({ agent })` wins over this. |
419
+ | `PATHGRADE_CODEX_TRANSPORT` | Fallback Codex transport (`exec` or `app-server`). `createAgent({ transport })` wins over this. |
420
+ | `PATHGRADE_VERBOSE` | `1` enables live per-turn streaming to stderr |
421
+ | `PATHGRADE_DIAGNOSTICS` | `1` prints full diagnostics for passing evals too |
422
+ | `NO_COLOR` | Disable ANSI colors |
423
+
424
+ ### Experimental `node:test` Adapter
425
+
426
+ `node-test` is a small proof adapter for validating the runner boundary without Vitest. It is intentionally narrow: eval files import the `test` wrapper from `@wix/pathgrade/adapters/node-test`, run through Node's built-in test runner, and still produce the normal `.pathgrade/results.json` and trace artifacts.
427
+
428
+ ```ts
429
+ import { test } from '@wix/pathgrade/adapters/node-test';
430
+ import { createAgent, evaluate, check } from '@wix/pathgrade';
431
+
432
+ test('minimal node proof', async () => {
433
+ const agent = await createAgent({ workspace: process.cwd() });
434
+ await evaluate(agent, [check('passes', () => true)]);
435
+ });
436
+ ```
437
+
438
+ Run it with:
439
+
440
+ ```bash
441
+ pathgrade run --adapter=node-test
442
+ ```
443
+
444
+ This proof validates Pathgrade's adapter, lifecycle, result capture, and reporting boundaries. It does not imply Mocha, Playwright, or runnerless CLI support.
445
+
446
+ `pathgrade run` loads `.env` from the working directory automatically.
447
+
448
+ ## CI / GitHub Actions
449
+
450
+ Run evals on every PR and post results as a PR comment:
451
+
452
+ ```yaml
453
+ jobs:
454
+ eval:
455
+ runs-on: ubuntu-latest
456
+ permissions:
457
+ pull-requests: write
458
+ contents: read
459
+ steps:
460
+ - uses: actions/checkout@v4
461
+ with:
462
+ fetch-depth: 0 # required for affected selection
463
+ - uses: actions/setup-node@v4
464
+ with:
465
+ node-version: '20'
466
+ - run: npm ci
467
+
468
+ - name: Run affected evals
469
+ env:
470
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
471
+ run: npx pathgrade run --changed
472
+
473
+ - name: Post PR report
474
+ if: always()
475
+ env:
476
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
477
+ run: npx pathgrade report
478
+
479
+ - uses: actions/upload-artifact@v4
480
+ if: always()
481
+ with:
482
+ name: pathgrade-reports
483
+ path: .pathgrade/
484
+ ```
485
+
486
+ - `fetch-depth: 0` is required for `--changed`; shallow clones break merge-base resolution.
487
+ - Evals under a `SKILL.md` are tracked automatically; use `__pathgradeMeta` for cross-skill or non-standard dependencies.
488
+ - Set `ci: { threshold: 0.8 }` in `pathgrade.config.ts` to fail the run when the mean test score drops below your threshold.
489
+
490
+ See the [User Guide - CI Integration](docs/USER_GUIDE.md#ci-integration) for the full reference.
491
+
492
+ ## Links
493
+
494
+ - [User Guide](docs/USER_GUIDE.md) - full API reference and usage patterns
495
+ - Examples:
496
+ - [start-chat](examples/start-chat/) - multi-turn conversation
497
+ - [sdk-showcase](examples/sdk-showcase/) - advanced SDK features in one suite
498
+ - [tool-judge-demo](examples/tool-judge-demo/) - `judge({ tools })` reading workspace artifacts
499
+
500
+ ## Note on AI provider dependencies
501
+
502
+ Pathgrade is released under the MIT license. Pathgrade can use third-party AI agent tools and SDKs to run evaluations, including `@anthropic-ai/claude-agent-sdk` for Claude, Codex CLI for Codex, and `cursor-agent` for Cursor. These tools, SDKs, hosted services, and related authentication methods are governed by their respective provider terms, which are separate from Pathgrade's MIT license.
503
+
504
+ In particular, `@anthropic-ai/claude-agent-sdk` is governed by Anthropic's Commercial Terms of Service, except where Anthropic specifies a different license for a specific component or dependency.
505
+
506
+ ## License
507
+
508
+ MIT
@@ -0,0 +1,17 @@
1
+ export { runnerAdapterContractVersion, type AdapterCaseContext, type AdapterDiagnostic, type AdapterDiscoveryInput, type AdapterDiscoveryResult, type AdapterEvalUnit, type AdapterInvocationInput, type AdapterLifecycleHooks, type AdapterRunHandle, type AdapterRunStatus, type EvalResultEvent, type RunnerAdapter, } from '../runners/adapter.js';
2
+ export type { RunnerInvocationAdapter, RunnerInvocationInput, } from '../runners/invocation.js';
3
+ export type { AttemptOutcome, AttemptRecord, AssertionRecord, Diagnostic, EvalUnitRecord, EvaluationRecord, GroupingHint, NativeReference, NormalizedRunModel, NormalizedRunSnapshot, RunCaseRecord, RunCaseState, RunRecord, RunStatus, ScoringPolicy, SnapshotCompleteness, } from '../runners/model.js';
4
+ export { buildNormalizedRunSnapshotFromReportGroups } from '../runners/model-builders.js';
5
+ export { validateNormalizedRunSnapshot } from '../runners/model-validation.js';
6
+ export { projectNormalizedRunSnapshotToReportInput } from '../runners/report-projection.js';
7
+ export { runWithAdapter, type PathgradeRunOptions, type AdapterReporterMode } from '../runners/orchestrator.js';
8
+ export { createRunnerLifecycleHooks } from '../runners/lifecycle-hooks.js';
9
+ export { discoverPathgradeEvalFiles } from '../evals/discovery.js';
10
+ export { DEFAULT_EVAL_EXCLUDE, DEFAULT_EVAL_INCLUDE, defaultPathgradeConfig, resolvePathgradeConfig, type PathgradeConfig, type ResolvedPathgradeConfig, } from '../config/pathgrade.js';
11
+ export { readSidecar } from '../affected/sidecar.js';
12
+ export { getPathgradeDir } from '../reporters/results-path.js';
13
+ export { printReportSummary } from '../reporters/report-summary.js';
14
+ export { fmt } from '../utils/cli.js';
15
+ export { getCurrentCaseContext, installCaseContextProvider, runWithCaseContext, type CaseContext, type CaseContextProvider, type CaseContextProviderHandle, type CaseContextScope, type CurrentCaseContext, } from '../sdk/case-context.js';
16
+ export { subscribeToEvalResults, type EvalResultObserver, type ResultObserverHandle, type ResultObserverOptions, type ResultObserverOwner, } from '../sdk/result-capture.js';
17
+ export type { Agent, PathgradeTestMeta, RecordedEvalResult, } from '../sdk/types.js';
@@ -0,0 +1,14 @@
1
+ export { runnerAdapterContractVersion, } from '../runners/adapter.js';
2
+ export { buildNormalizedRunSnapshotFromReportGroups } from '../runners/model-builders.js';
3
+ export { validateNormalizedRunSnapshot } from '../runners/model-validation.js';
4
+ export { projectNormalizedRunSnapshotToReportInput } from '../runners/report-projection.js';
5
+ export { runWithAdapter } from '../runners/orchestrator.js';
6
+ export { createRunnerLifecycleHooks } from '../runners/lifecycle-hooks.js';
7
+ export { discoverPathgradeEvalFiles } from '../evals/discovery.js';
8
+ export { DEFAULT_EVAL_EXCLUDE, DEFAULT_EVAL_INCLUDE, defaultPathgradeConfig, resolvePathgradeConfig, } from '../config/pathgrade.js';
9
+ export { readSidecar } from '../affected/sidecar.js';
10
+ export { getPathgradeDir } from '../reporters/results-path.js';
11
+ export { printReportSummary } from '../reporters/report-summary.js';
12
+ export { fmt } from '../utils/cli.js';
13
+ export { getCurrentCaseContext, installCaseContextProvider, runWithCaseContext, } from '../sdk/case-context.js';
14
+ export { subscribeToEvalResults, } from '../sdk/result-capture.js';
@@ -0,0 +1,3 @@
1
+ export { createJestAdapter, createPathgradeAdapter } from './runner-adapter.js';
2
+ export { createJestInvocationAdapter, createPathgradeInvocationAdapter } from './invocation-adapter.js';
3
+ export { default as PathgradeJestReporter } from './reporter.js';
@@ -0,0 +1,3 @@
1
+ export { createJestAdapter, createPathgradeAdapter } from './runner-adapter.js';
2
+ export { createJestInvocationAdapter, createPathgradeInvocationAdapter } from './invocation-adapter.js';
3
+ export { default as PathgradeJestReporter } from './reporter.js';
@@ -0,0 +1,15 @@
1
+ import { type ResolvedPathgradeConfig, type RunnerInvocationAdapter } from '@wix/pathgrade/adapter-kit';
2
+ export interface SpawnJestRequest {
3
+ argv: string[];
4
+ env: NodeJS.ProcessEnv;
5
+ cwd: string;
6
+ }
7
+ export type SpawnJest = (req: SpawnJestRequest) => Promise<number> | number;
8
+ export declare function createJestInvocationAdapter(input: {
9
+ config: ResolvedPathgradeConfig;
10
+ spawnJest?: SpawnJest;
11
+ }): RunnerInvocationAdapter;
12
+ export declare function createPathgradeInvocationAdapter(input: {
13
+ config: ResolvedPathgradeConfig;
14
+ }): RunnerInvocationAdapter;
15
+ export declare function resolveLocalJestBinPath(cwd: string): string;