evalproof 0.3.0__tar.gz → 0.4.0__tar.gz

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 (84) hide show
  1. {evalproof-0.3.0 → evalproof-0.4.0}/CHANGELOG.md +67 -1
  2. {evalproof-0.3.0 → evalproof-0.4.0}/PKG-INFO +169 -5
  3. {evalproof-0.3.0 → evalproof-0.4.0}/README.md +168 -4
  4. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/_version.py +1 -1
  5. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/cli.py +59 -1
  6. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/compare.py +29 -0
  7. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/config.py +3 -0
  8. evalproof-0.4.0/src/evalproof/doctor.py +130 -0
  9. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/gate.py +20 -6
  10. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/providers/__init__.py +40 -2
  11. evalproof-0.4.0/src/evalproof/providers/agent_cli.py +617 -0
  12. evalproof-0.4.0/src/evalproof/providers/agent_cli_experimental.py +452 -0
  13. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/providers/base.py +8 -2
  14. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/report.py +11 -0
  15. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/runner.py +73 -6
  16. evalproof-0.4.0/src/evalproof/scaffold.py +369 -0
  17. evalproof-0.4.0/src/evalproof/templates/SKILL.md +86 -0
  18. evalproof-0.4.0/src/evalproof/templates/cases.jsonl +3 -0
  19. evalproof-0.4.0/src/evalproof/templates/evals-readme.md +11 -0
  20. evalproof-0.4.0/src/evalproof/templates/gitignore +3 -0
  21. evalproof-0.4.0/src/evalproof/templates/suite.yaml +70 -0
  22. evalproof-0.4.0/src/evalproof/templates/workflow-api-key.yml +74 -0
  23. evalproof-0.4.0/src/evalproof/templates/workflow-keyless.yml +67 -0
  24. evalproof-0.4.0/tests/fakecli.py +260 -0
  25. evalproof-0.4.0/tests/fakecli_experimental.py +207 -0
  26. evalproof-0.4.0/tests/test_agent_cli.py +810 -0
  27. evalproof-0.4.0/tests/test_agent_cli_experimental.py +869 -0
  28. evalproof-0.4.0/tests/test_doctor.py +251 -0
  29. evalproof-0.4.0/tests/test_init.py +699 -0
  30. evalproof-0.4.0/tests/test_judge_fingerprint.py +272 -0
  31. evalproof-0.4.0/tests/test_live_agent_cli.py +47 -0
  32. {evalproof-0.3.0 → evalproof-0.4.0}/.gitignore +0 -0
  33. {evalproof-0.3.0 → evalproof-0.4.0}/CITATION.cff +0 -0
  34. {evalproof-0.3.0 → evalproof-0.4.0}/LICENSE +0 -0
  35. {evalproof-0.3.0 → evalproof-0.4.0}/NOTICE +0 -0
  36. {evalproof-0.3.0 → evalproof-0.4.0}/pyproject.toml +0 -0
  37. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/__init__.py +0 -0
  38. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/cache.py +0 -0
  39. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/calibration.py +0 -0
  40. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/dataset.py +0 -0
  41. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/importers/__init__.py +0 -0
  42. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/importers/_common.py +0 -0
  43. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/importers/deepeval.py +0 -0
  44. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/importers/inspect_ai.py +0 -0
  45. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/importers/lm_eval.py +0 -0
  46. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/importers/promptfoo.py +0 -0
  47. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/importers/table.py +0 -0
  48. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/models.py +0 -0
  49. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/providers/anthropic.py +0 -0
  50. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/providers/ollama.py +0 -0
  51. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/providers/openai.py +0 -0
  52. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/providers/profiles.py +0 -0
  53. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/py.typed +0 -0
  54. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/scorers/__init__.py +0 -0
  55. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/scorers/deterministic.py +0 -0
  56. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/scorers/judge.py +0 -0
  57. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/scorers/retrieval.py +0 -0
  58. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/stats.py +0 -0
  59. {evalproof-0.3.0 → evalproof-0.4.0}/src/evalproof/systems.py +0 -0
  60. {evalproof-0.3.0 → evalproof-0.4.0}/tests/__init__.py +0 -0
  61. {evalproof-0.3.0 → evalproof-0.4.0}/tests/conftest.py +0 -0
  62. {evalproof-0.3.0 → evalproof-0.4.0}/tests/fixtures/README.md +0 -0
  63. {evalproof-0.3.0 → evalproof-0.4.0}/tests/fixtures/deepeval/test_run.json +0 -0
  64. {evalproof-0.3.0 → evalproof-0.4.0}/tests/fixtures/inspect/log_formats.json +0 -0
  65. {evalproof-0.3.0 → evalproof-0.4.0}/tests/fixtures/lm-eval/gpt2/samples_hellaswag_2024-05-01T10-20-30.123456.jsonl +0 -0
  66. {evalproof-0.3.0 → evalproof-0.4.0}/tests/fixtures/lm-eval/llama3/samples_gsm8k_2024-05-01T10-20-30.123456.jsonl +0 -0
  67. {evalproof-0.3.0 → evalproof-0.4.0}/tests/fixtures/promptfoo/sample-export.json +0 -0
  68. {evalproof-0.3.0 → evalproof-0.4.0}/tests/fixtures/table/long.csv +0 -0
  69. {evalproof-0.3.0 → evalproof-0.4.0}/tests/fixtures/table/long.jsonl +0 -0
  70. {evalproof-0.3.0 → evalproof-0.4.0}/tests/fixtures/table/wide.csv +0 -0
  71. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_action.py +0 -0
  72. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_cache_systems.py +0 -0
  73. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_calibration.py +0 -0
  74. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_compare_gate_report.py +0 -0
  75. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_config_dataset.py +0 -0
  76. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_holm_gate.py +0 -0
  77. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_importers.py +0 -0
  78. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_providers.py +0 -0
  79. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_public_api.py +0 -0
  80. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_release_fixes.py +0 -0
  81. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_runner_cli.py +0 -0
  82. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_scorers.py +0 -0
  83. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_security.py +0 -0
  84. {evalproof-0.3.0 → evalproof-0.4.0}/tests/test_stats.py +0 -0
@@ -4,6 +4,71 @@ All notable changes are listed here. The project follows [Semantic Versioning](h
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.4.0] — 2026-11-13
8
+
9
+ ### Added
10
+
11
+ - `claude-code:<model>` and `codex:<model>` providers: run your own logged-in Claude Code or
12
+ Codex CLI as a judge or a system under test, with no API key. Each call runs in a fresh
13
+ empty directory with the agent's tools, settings, MCP servers, skills and memory switched
14
+ off and no permission-bypass flag; API-key variables are removed from the child's
15
+ environment unless `api_key_env` names one or `EVALPROOF_CLI_AUTH=api-key` keeps them.
16
+ Neither CLI takes a temperature, seed or token limit, so those params are ignored (Codex
17
+ maps `think` to its reasoning effort). Claude Code runs with no tools; Codex still offers a
18
+ JavaScript isolate, sub-agents and a question to the user, so any item in its output other
19
+ than a message, reasoning, an error or a plan fails the call as an isolation break, and a
20
+ codex too old for the flags evalproof sets gets a message to upgrade. ADR 0004 has the
21
+ reasoning.
22
+ - `EVALPROOF_CLI_AUTH` (`login`, the default, or `api-key`) chooses whether a CLI provider
23
+ uses the subscription login or its own API key variable; `evalproof doctor` prints the mode
24
+ in effect and warns when `api-key` has no key to use. `api_key_env` still wins.
25
+ - `evalproof doctor [suite.yaml]`: reports each CLI's path, version, login mode and the
26
+ isolation flags it will use, without a model call, and exits 1 when a target of the suite
27
+ is not usable. Without a suite it surveys the installed CLIs and exits 1 only when none is
28
+ usable. It prints no account details.
29
+ - Run facts for CLI targets in `runner.environment`: `{judge,system}_cli`,
30
+ `{judge,system}_cli_version`, `{judge,system}_isolation` and `{judge,system}_model_resolved`
31
+ (the model ids that answered, sorted, cached replies included). The response cache keys on
32
+ the CLI fingerprint, so a CLI upgrade is a cache miss.
33
+ - `compare` reports `same_judge`, `judge_changes` and `system_changes`, and the gate refuses
34
+ to decide (exit 4) when the judge changed between the runs. `--allow-judge-change` or
35
+ `gate.allow_judge_change: true` accepts the change and keeps a warning.
36
+ - Experimental `opencode:<provider>/<model>`, `cursor:<model>` and `copilot:<model>`
37
+ providers, on the same footing as the CLI providers above (fresh empty directory, the CLI's
38
+ key variable removed unless `api_key_env` names one, bounded runtime, CLI fingerprint with the
39
+ isolation tags `opencode/0`, `cursor/0`, `copilot/0`). Each prints one warning on stderr the
40
+ first time it is used. Only OpenCode was exercised for real, and only against a local ollama
41
+ model; Cursor (not logged in) and Copilot (not installed) follow their documentation, and the
42
+ README's "Experimental providers" says what was and was not verified. `evalproof doctor`
43
+ without a suite now also lists whichever of these CLIs are on `PATH`.
44
+ - `evalproof.providers.TransientError`, a `ProviderError` that `with_retries` retries; the CLI
45
+ providers raise it for rate limits and server errors.
46
+ - `tests/test_live_agent_cli.py`: one real call per installed CLI, skipped unless
47
+ `EVALPROOF_LIVE=1`.
48
+ - `evalproof init [--dir PATH] [--judge TARGET] [--ci keyless|api-key|none] [--no-skill]
49
+ [--force]`: writes `evals/suite.yaml`, three sample cases in `evals/cases.jsonl`, an
50
+ `evals/README.md`, a GitHub Actions workflow at `.github/workflows/evalproof.yml` and an
51
+ agent skill at `.agents/skills/evalproof/SKILL.md` and `.claude/skills/evalproof/SKILL.md`.
52
+ The judge is the first of `claude`, `codex`, `ollama` found on `PATH`, else a hosted
53
+ Anthropic model. The keyless workflow makes no model calls and gates the committed
54
+ `evals/runs/latest.json` against the base branch; the api-key workflow runs the suite in CI
55
+ (with `EVALPROOF_CLI_AUTH: api-key` for a CLI judge, so no second suite file is written)
56
+ and pins the judge CLI to the version found at init time. Existing files are kept unless
57
+ `--force` is passed, nothing is written outside `--dir`, and no model is called.
58
+
59
+ ### Changed
60
+
61
+ - A gate that passed in 0.3.0 can now exit 4 when the judge's target, CLI version, isolation
62
+ or resolved model differs between two runs that both carry these keys. Runs written before
63
+ this release carry none and compare as before.
64
+ - A changed system target or CLI version between two runs now adds a warning, so the status
65
+ is `warn` instead of `pass` (the exit code stays 0).
66
+ - `runner.environment` records `judge_target` and `system_target` for every model target (not
67
+ only CLI ones), and the system target it records is the one in effect, with any override
68
+ applied.
69
+ - The gate's "too few paired cases" reason now says how to fix it (add cases, or lower
70
+ `gate.min_cases`).
71
+
7
72
  ## [0.3.0] — 2026-11-12
8
73
 
9
74
  ### Added
@@ -179,7 +244,8 @@ First release.
179
244
  - Two examples: an offline ticket router (CI's gate contract) and a product support
180
245
  assistant judged by a local model, with measurements.
181
246
 
182
- [Unreleased]: https://github.com/technobasant/evalproof/compare/v0.3.0...HEAD
247
+ [Unreleased]: https://github.com/technobasant/evalproof/compare/v0.4.0...HEAD
248
+ [0.4.0]: https://github.com/technobasant/evalproof/compare/v0.3.0...v0.4.0
183
249
  [0.3.0]: https://github.com/technobasant/evalproof/compare/v0.2.1...v0.3.0
184
250
  [0.2.1]: https://github.com/technobasant/evalproof/compare/v0.2.0...v0.2.1
185
251
  [0.2.0]: https://github.com/technobasant/evalproof/compare/v0.1.2...v0.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: evalproof
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Treat LLM evaluation as measurement: calibrate the judge, correct the pass rate, gate CI on significant regressions.
5
5
  Project-URL: Homepage, https://github.com/technobasant/evalproof
6
6
  Project-URL: Documentation, https://github.com/technobasant/evalproof#readme
@@ -100,6 +100,23 @@ questions, one Apple M5. Full method and every table in [MEASUREMENTS.md](https:
100
100
 
101
101
  ## Five-minute quickstart
102
102
 
103
+ In your own repository, one command writes a starter suite, a CI workflow and an agent skill,
104
+ and a second runs it:
105
+
106
+ ```bash
107
+ uvx evalproof init
108
+ uvx evalproof run evals/suite.yaml --out evals/runs/latest.json
109
+ ```
110
+
111
+ `init` picks the judge from what is on your `PATH`: a logged-in `claude`, then `codex`, then
112
+ Ollama, then a hosted Anthropic model with `ANTHROPIC_API_KEY`. It writes `evals/` (the
113
+ suite, three sample cases, a README), `.github/workflows/evalproof.yml` (`--ci keyless`, the
114
+ default, makes no model calls in CI; `--ci api-key` runs the suite there; `--ci none` writes
115
+ no workflow) and a skill for coding agents at `.agents/skills/evalproof/SKILL.md` and
116
+ `.claude/skills/evalproof/SKILL.md` (`--no-skill` skips it). It prints one line per file,
117
+ never overwrites one without `--force`, and makes no model call. Replace the sample cases
118
+ with your own: the gate needs 20 before it will decide.
119
+
103
120
  The offline example lives in the repository:
104
121
 
105
122
  ```bash
@@ -135,7 +152,11 @@ The target is `<provider>:<model>`; the model name is everything after the first
135
152
  `openrouter:anthropic/claude-sonnet-4.5` and `ollama:qwen3:8b` both work. Two native adapters
136
153
  (`ollama`, `anthropic`) and one OpenAI-compatible adapter driven by a table of provider
137
154
  profiles ([`providers/profiles.py`](https://github.com/technobasant/evalproof/blob/main/src/evalproof/providers/profiles.py), each row cites the
138
- provider's documentation). Plain HTTP throughout, no vendor SDK.
155
+ provider's documentation). Plain HTTP throughout, no vendor SDK. Two more targets run a coding
156
+ agent you already have logged in, `claude-code` and `codex`: see
157
+ [Use your Claude Code or Codex login](#use-your-claude-code-or-codex-login). Three experimental
158
+ ones run OpenCode, Cursor and GitHub Copilot the same way: see
159
+ [Experimental providers](#experimental-providers).
139
160
 
140
161
  | Provider | Example target | Key variable | Where |
141
162
  |---|---|---|---|
@@ -156,6 +177,11 @@ provider's documentation). Plain HTTP throughout, no vendor SDK.
156
177
  | `openrouter` | `openrouter:anthropic/claude-sonnet-4.5` | `OPENROUTER_API_KEY` | hosted |
157
178
  | `cerebras` | `cerebras:gpt-oss-120b` | `CEREBRAS_API_KEY` | hosted |
158
179
  | `openai-compatible` | `openai-compatible:<model>` | optional, via `api_key_env` | any server, needs `base_url` |
180
+ | `claude-code` | `claude-code:haiku` | none: your Claude Code login | local CLI, see below |
181
+ | `codex` | `codex:gpt-6-luna` | none: your ChatGPT login | local CLI, see below |
182
+ | `opencode` | `opencode:ollama/qwen3:8b` | none: OpenCode's own login, per model provider | local CLI, experimental |
183
+ | `cursor` | `cursor:auto` | none: your Cursor login | local CLI, experimental |
184
+ | `copilot` | `copilot:gpt-5` | none: your GitHub Copilot login | local CLI, experimental |
159
185
 
160
186
  ```yaml
161
187
  system:
@@ -186,6 +212,134 @@ system:
186
212
  * AWS Bedrock and Google Vertex AI sign requests with cloud credentials, which evalproof does
187
213
  not implement. Use a gateway's OpenAI-compatible endpoint through `openai-compatible`.
188
214
 
215
+ ## Use your Claude Code or Codex login
216
+
217
+ If Claude Code or Codex is already logged in on your machine, either can be the judge or the
218
+ system under test, with no API key:
219
+
220
+ ```yaml
221
+ judge:
222
+ target: claude-code:haiku # an alias (haiku, sonnet, opus) or a full model id
223
+ system:
224
+ target: codex:gpt-6-luna
225
+ concurrency: 4
226
+ ```
227
+
228
+ Run `evalproof doctor suite.yaml` first. It prints each CLI's path, version, login mode and
229
+ the isolation flags it will pass, and exits 1 if a target of the suite is not usable. Without a
230
+ suite it surveys every CLI it knows that is installed, and exits 1 only when none is usable. It
231
+ makes no model call.
232
+
233
+ evalproof runs your own `claude` or `codex` binary as a subprocess, sends the prompt on stdin
234
+ and reads the JSON it prints. It never reads a token file or the keychain. The environment
235
+ variable `EVALPROOF_CLI_AUTH` says which credentials the CLI uses:
236
+
237
+ * `login` (the default, also when it is unset or empty) removes the CLI's key variables from
238
+ the call (`ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` for Claude Code, `OPENAI_API_KEY`
239
+ and `CODEX_API_KEY` for Codex), so the login is what gets used.
240
+ * `api-key` leaves them in place, so the CLI uses its own key. This is the route for CI.
241
+ * Any other value is an error.
242
+
243
+ `api_key_env` in the suite works in either mode and wins: it passes that variable to the CLI as
244
+ its own key variable. The mode is not part of the run's fingerprint or the cache key, because
245
+ it is the same model either way. `evalproof doctor` prints the mode in effect and warns when
246
+ `api-key` is chosen but the CLI's key variable is not set.
247
+
248
+ * **Isolation is a requirement, not an option.** A coding agent is not a model endpoint. Left
249
+ alone it loads the project's `CLAUDE.md` or `AGENTS.md`, your memory, plugins, MCP servers
250
+ and skills into the prompt, and it can run commands. A judge that has read your project's
251
+ instructions is not a judge. So every call runs in a fresh empty directory, with the
252
+ agent's own system prompt replaced by yours (or one neutral line), its tools, settings, MCP
253
+ servers and skills switched off, and no permission-bypass flag. `evalproof doctor` prints
254
+ the exact flags. The two CLIs do not isolate equally well:
255
+ * Claude Code is run with **no tools at all** (`--tools ''`).
256
+ * Codex runs in a read-only sandbox with its shell, plugins, apps, hooks, multi-agent mode,
257
+ sleep and image tools, bundled skills and your user config and rules switched off, but
258
+ codex-cli 0.167.2 has no flag that removes every tool. Still offered are a JavaScript
259
+ isolate (`exec`: no Node, no file system, no network, with a few built-in helpers such as
260
+ the current time, MCP resource listing and `apply_patch`), sub-agents that get the same
261
+ restricted tools, and a question to the user, which cannot be answered in exec mode. **Any
262
+ tool use fails the call**: if the output holds an item that is not a message, reasoning,
263
+ an error or a plan, evalproof raises `codex used a tool (<type>); isolation broken` and
264
+ discards the reply. A codex too old to know a flag evalproof sets exits with `Unknown
265
+ feature flag`, and evalproof says to upgrade it (it was tested with codex-cli 0.167.2).
266
+ * **No temperature, seed or token limit.** Neither CLI exposes one, so `temperature`, `seed`
267
+ and `max_tokens` are ignored. `think` maps to Codex's reasoning effort (`false` is `low`,
268
+ `true` is `medium`; `params.extra.reasoning_effort` sets it directly). A run is therefore
269
+ not repeatable the way a seeded local run is, so the run records the CLI, its version, the
270
+ isolation scheme and the model id that actually answered, and the cache keys on the CLI
271
+ version. To see how much a judge wobbles, score the same outputs twice with `--no-cache`, or
272
+ [calibrate](#concepts) it against labels.
273
+ * **A changed judge is not comparable.** `compare` and the gate refuse (exit 4) when the judge's
274
+ target, CLI version, isolation or resolved model differs between the two runs, because the
275
+ scores then come from a different instrument. Pass `--allow-judge-change` or set
276
+ `gate.allow_judge_change: true` to accept it. A changed system target or CLI version only
277
+ warns, since that is usually the change under test. Runs written before 0.4.0 carry none of
278
+ these keys and compare as before.
279
+ * **Latency and cost.** A call takes about 1.4 s on `claude-code:haiku` and about 4 s on
280
+ Codex, mostly start-up, so set `concurrency`. There is no API bill; the calls count against
281
+ your subscription's usage limits.
282
+ * **Terms.** OpenAI documents Codex CLI use on a ChatGPT plan, including automation.
283
+ Anthropic's terms allow a user to run the unmodified Claude Code binary on their own
284
+ subscription; its plan limits assume ordinary individual use, and it forbids third-party
285
+ products from routing other users' requests through subscription credentials. evalproof
286
+ never touches credentials: it runs your own `claude` binary, on your machine, for your own
287
+ evals. For CI or team-wide use, use an API key (`EVALPROOF_CLI_AUTH=api-key`, or
288
+ `api_key_env`). See Anthropic's
289
+ [legal and compliance page](https://code.claude.com/docs/en/legal-and-compliance). Nothing
290
+ uses a CLI unless a suite names a `claude-code:` or `codex:` target (or an experimental one,
291
+ below).
292
+
293
+ ### Experimental providers
294
+
295
+ `opencode:`, `cursor:` and `copilot:` follow the same rules as the two above: your own logged-in
296
+ binary, run as a subprocess in a fresh empty directory, its API-key variable removed from the
297
+ child's environment unless `api_key_env` names one, a bounded runtime, and the CLI, its version
298
+ and an isolation tag (`opencode/0`, `cursor/0`, `copilot/0`) in the run and in the cache key.
299
+ They are experimental because their isolation is less proven than Claude Code's and Codex's.
300
+ Each prints `evalproof: <name> provider is experimental` on stderr the first time it is used in
301
+ a process. Like the other two they ignore `temperature`, `seed` and `max_tokens`, and
302
+ `evalproof doctor` without a suite lists each of them whose binary is on `PATH`. What was
303
+ checked, and what was not:
304
+
305
+ * **`opencode:<provider>/<model>`** runs `opencode run --standalone --archived --format json`
306
+ with a custom agent that has every tool off and every permission denied, your system text as
307
+ that agent's prompt, and project config, the global config directory and the Claude Code
308
+ compatibility files switched off. The prompt is on stdin. *Verified* only against local
309
+ ollama models (`qwen3:8b`, a handful of calls): no tool event, a canary `AGENTS.md` in the
310
+ project not read, a canary `AGENTS.md` in the global config directory read by default and not
311
+ read with these settings, the parser and the error path. *Not verified:* any hosted model;
312
+ whether `~/.claude/CLAUDE.md` is read (that test was inconclusive); whether the empty
313
+ `OPENCODE_CONFIG_DIR` hides a login stored there; the token counts (input adds cache reads
314
+ and writes, output adds reasoning, summed per field name). OpenCode has no single login, so
315
+ `doctor` prints `auth: not checked`. Only `OPENCODE_API_KEY` is removed from the environment:
316
+ the key of the model's own provider (`ANTHROPIC_API_KEY` for `opencode:anthropic/...`) is
317
+ left, because naming that provider in the model id is choosing its key, and removing it would
318
+ break CI. `--archived` keeps a session out of OpenCode's lists but does not delete it, so
319
+ sessions accumulate in OpenCode's data directory (each run is a private `--standalone`
320
+ server in a temporary project); prune them yourself.
321
+ * **`cursor:<model>`** runs `cursor-agent -p --mode ask --trust` with an empty `--workspace`.
322
+ *Not verified:* Cursor was not logged in on the test machine, so no call succeeded. The flags
323
+ come from the CLI's help and documentation, and the parser from its source; it is lenient (a
324
+ JSON result object, an array of events, terminal noise around it) and an unexpected shape
325
+ fails with a message that says what was expected. The empty workspace is the only isolation:
326
+ no flag ignores rules files, user-level rules were not checked, and whether ask mode really
327
+ has no tools is untested. There is no system-prompt option, so your system text goes in front
328
+ of the prompt under a heading, and the prompt is an argument (nothing says the CLI reads
329
+ stdin), so it is visible in `ps` while the call runs and a prompt over 100,000 bytes is
330
+ refused. The key variable is `CURSOR_API_KEY`, passed through the environment, never as
331
+ `--api-key`. `doctor` reads `cursor-agent about` for the login state (`status` hung) and
332
+ prints no account.
333
+ * **`copilot:<model>`** runs `copilot -s --no-auto-update --no-ask-user --available-tools= -p`.
334
+ *Not verified:* Copilot CLI was not installed on the test machine; every flag comes from
335
+ GitHub's documentation. `--available-tools=` is an allow-list with nothing in it, the most
336
+ restrictive documented tool option; whether the CLI accepts an empty list was not tried. No
337
+ `--allow-*` flag is ever passed, so a tool that needs approval cannot get it. The reply is
338
+ plain text, so token counts are 0. `COPILOT_HOME` is not redirected, so user-level Copilot
339
+ instructions may still be read. System text and prompt travel as for Cursor. Only
340
+ `COPILOT_GITHUB_TOKEN` is removed; `GH_TOKEN` and `GITHUB_TOKEN` are left because `gh` and CI
341
+ set them for other reasons. `doctor` prints `auth: not checked`.
342
+
189
343
  ## Concepts
190
344
 
191
345
  **Suite** — one YAML file: a JSONL golden set, the system under test, scorers, an optional
@@ -210,7 +364,8 @@ gate: {metric: judge, max_drop: 0.03, alpha: 0.05, min_cases: 40, max_p95_latenc
210
364
 
211
365
  **Run** — `evalproof run suite.yaml` writes one self-describing JSON file: every case's
212
366
  output, latency, tokens and scores, plus the dataset hash, seed, Ollama version and model
213
- digests.
367
+ digests, and for a `claude-code` or `codex` target the CLI, its version and the model that
368
+ answered.
214
369
 
215
370
  **Scorers** — `exact_match`, `contains`, `regex` (accept/reject answer keys),
216
371
  `json_schema`, `numeric_tolerance`, `recall_at_k`, `mrr`, `ndcg`, and `llm_judge`: a strict
@@ -235,7 +390,7 @@ orders and reports order-dependent verdicts as ties.
235
390
  | 0 | pass — possibly with warnings (a big drop that is not significant; a significant drop within tolerance) |
236
391
  | 1 | quality regression: drop ≥ `max_drop` **and** p < `alpha` |
237
392
  | 3 | budget breach: candidate p95 latency or cost over the limit |
238
- | 4 | cannot gate: fewer than `min_cases` paired cases, or the runs used different data |
393
+ | 4 | cannot gate: fewer than `min_cases` paired cases, the runs used different data, or the judge changed (unless `--allow-judge-change`) |
239
394
  | 2 | usage or configuration error |
240
395
 
241
396
  The gate checks one metric by default. To gate several, repeat `--metric` or list them in
@@ -322,6 +477,7 @@ The architecture diagram is in the [README on GitHub](https://github.com/technob
322
477
  | Local Ollama by default, HTTP-only providers, content-addressed cache | Hosted APIs and vendor SDKs | Reproducible, free to re-run, tied to a model digest; no SDK dependency | [0002](https://github.com/technobasant/evalproof/blob/main/docs/adr/0002-local-first-providers.md) |
323
478
  | Gate on "large AND significant", on paired cases | "Fail if the score dropped by X" | On a few dozen cases, X-point drops are mostly noise, and a noisy gate gets switched off | [0003](https://github.com/technobasant/evalproof/blob/main/docs/adr/0003-gate-on-significance-not-delta.md) |
324
479
  | One metric by default; several with Holm correction | Gate every metric at α each | Testing many metrics at α = 0.05 each inflates false failures | 0003 |
480
+ | Run a coding-agent CLI as a provider only in isolation, and gate on its fingerprint | Pass the CLI's defaults through, or ignore its version | A CLI that reads your project's instructions is not a judge, and a silent upgrade changes the instrument | [0004](https://github.com/technobasant/evalproof/blob/main/docs/adr/0004-agent-clis-as-providers.md) |
325
481
  | Pointwise verdicts for gating; pairwise as a diagnostic | Pairwise preference for gating | Measured: 42% of pairwise verdicts flipped with answer order, with a strong length preference | [MEASUREMENTS §6](https://github.com/technobasant/evalproof/blob/main/MEASUREMENTS.md) |
326
482
  | numpy and the standard library for statistics | SciPy, statsmodels | Small enough to read and to test against hand-worked values | — |
327
483
 
@@ -331,7 +487,7 @@ The composite action runs `compare --gate`, appends the report to the job summar
331
487
  fails the job on exit codes 1, 3 or 4:
332
488
 
333
489
  ```yaml
334
- - uses: technobasant/evalproof@v0.3.0
490
+ - uses: technobasant/evalproof@v0.4.0
335
491
  with:
336
492
  baseline: eval/baseline.json # produced on main, e.g. as a build artifact
337
493
  candidate: eval/candidate.json # produced on this branch
@@ -342,6 +498,14 @@ fails the job on exit codes 1, 3 or 4:
342
498
  This repository's own CI runs the offline example on every commit and asserts that the
343
499
  gate returns 1 for the real regression and 0 for the noisy and refactored candidates.
344
500
 
501
+ If the judge is a Claude Code or Codex CLI, `evalproof init --ci api-key` writes a workflow that
502
+ installs the CLI at the version found at init time and runs `evals/suite.yaml` as it is. The run
503
+ step sets `EVALPROOF_CLI_AUTH: api-key` and the repository secret (`ANTHROPIC_API_KEY` for
504
+ Claude Code, `OPENAI_API_KEY` for Codex), so the CLI uses that key instead of a login. The
505
+ suite names no key and there is no second suite file; a suite with an `api_key_env` still uses
506
+ that variable. Keep the pinned CLI version equal to the one in your baseline run, because the
507
+ gate exits 4 when the judge's CLI version differs.
508
+
345
509
  ## How it relates to other tools
346
510
 
347
511
  [promptfoo](https://www.promptfoo.dev/docs/intro/) (open-source, local evals of prompts,
@@ -64,6 +64,23 @@ questions, one Apple M5. Full method and every table in [MEASUREMENTS.md](MEASUR
64
64
 
65
65
  ## Five-minute quickstart
66
66
 
67
+ In your own repository, one command writes a starter suite, a CI workflow and an agent skill,
68
+ and a second runs it:
69
+
70
+ ```bash
71
+ uvx evalproof init
72
+ uvx evalproof run evals/suite.yaml --out evals/runs/latest.json
73
+ ```
74
+
75
+ `init` picks the judge from what is on your `PATH`: a logged-in `claude`, then `codex`, then
76
+ Ollama, then a hosted Anthropic model with `ANTHROPIC_API_KEY`. It writes `evals/` (the
77
+ suite, three sample cases, a README), `.github/workflows/evalproof.yml` (`--ci keyless`, the
78
+ default, makes no model calls in CI; `--ci api-key` runs the suite there; `--ci none` writes
79
+ no workflow) and a skill for coding agents at `.agents/skills/evalproof/SKILL.md` and
80
+ `.claude/skills/evalproof/SKILL.md` (`--no-skill` skips it). It prints one line per file,
81
+ never overwrites one without `--force`, and makes no model call. Replace the sample cases
82
+ with your own: the gate needs 20 before it will decide.
83
+
67
84
  The offline example lives in the repository:
68
85
 
69
86
  ```bash
@@ -99,7 +116,11 @@ The target is `<provider>:<model>`; the model name is everything after the first
99
116
  `openrouter:anthropic/claude-sonnet-4.5` and `ollama:qwen3:8b` both work. Two native adapters
100
117
  (`ollama`, `anthropic`) and one OpenAI-compatible adapter driven by a table of provider
101
118
  profiles ([`providers/profiles.py`](src/evalproof/providers/profiles.py), each row cites the
102
- provider's documentation). Plain HTTP throughout, no vendor SDK.
119
+ provider's documentation). Plain HTTP throughout, no vendor SDK. Two more targets run a coding
120
+ agent you already have logged in, `claude-code` and `codex`: see
121
+ [Use your Claude Code or Codex login](#use-your-claude-code-or-codex-login). Three experimental
122
+ ones run OpenCode, Cursor and GitHub Copilot the same way: see
123
+ [Experimental providers](#experimental-providers).
103
124
 
104
125
  | Provider | Example target | Key variable | Where |
105
126
  |---|---|---|---|
@@ -120,6 +141,11 @@ provider's documentation). Plain HTTP throughout, no vendor SDK.
120
141
  | `openrouter` | `openrouter:anthropic/claude-sonnet-4.5` | `OPENROUTER_API_KEY` | hosted |
121
142
  | `cerebras` | `cerebras:gpt-oss-120b` | `CEREBRAS_API_KEY` | hosted |
122
143
  | `openai-compatible` | `openai-compatible:<model>` | optional, via `api_key_env` | any server, needs `base_url` |
144
+ | `claude-code` | `claude-code:haiku` | none: your Claude Code login | local CLI, see below |
145
+ | `codex` | `codex:gpt-6-luna` | none: your ChatGPT login | local CLI, see below |
146
+ | `opencode` | `opencode:ollama/qwen3:8b` | none: OpenCode's own login, per model provider | local CLI, experimental |
147
+ | `cursor` | `cursor:auto` | none: your Cursor login | local CLI, experimental |
148
+ | `copilot` | `copilot:gpt-5` | none: your GitHub Copilot login | local CLI, experimental |
123
149
 
124
150
  ```yaml
125
151
  system:
@@ -150,6 +176,134 @@ system:
150
176
  * AWS Bedrock and Google Vertex AI sign requests with cloud credentials, which evalproof does
151
177
  not implement. Use a gateway's OpenAI-compatible endpoint through `openai-compatible`.
152
178
 
179
+ ## Use your Claude Code or Codex login
180
+
181
+ If Claude Code or Codex is already logged in on your machine, either can be the judge or the
182
+ system under test, with no API key:
183
+
184
+ ```yaml
185
+ judge:
186
+ target: claude-code:haiku # an alias (haiku, sonnet, opus) or a full model id
187
+ system:
188
+ target: codex:gpt-6-luna
189
+ concurrency: 4
190
+ ```
191
+
192
+ Run `evalproof doctor suite.yaml` first. It prints each CLI's path, version, login mode and
193
+ the isolation flags it will pass, and exits 1 if a target of the suite is not usable. Without a
194
+ suite it surveys every CLI it knows that is installed, and exits 1 only when none is usable. It
195
+ makes no model call.
196
+
197
+ evalproof runs your own `claude` or `codex` binary as a subprocess, sends the prompt on stdin
198
+ and reads the JSON it prints. It never reads a token file or the keychain. The environment
199
+ variable `EVALPROOF_CLI_AUTH` says which credentials the CLI uses:
200
+
201
+ * `login` (the default, also when it is unset or empty) removes the CLI's key variables from
202
+ the call (`ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` for Claude Code, `OPENAI_API_KEY`
203
+ and `CODEX_API_KEY` for Codex), so the login is what gets used.
204
+ * `api-key` leaves them in place, so the CLI uses its own key. This is the route for CI.
205
+ * Any other value is an error.
206
+
207
+ `api_key_env` in the suite works in either mode and wins: it passes that variable to the CLI as
208
+ its own key variable. The mode is not part of the run's fingerprint or the cache key, because
209
+ it is the same model either way. `evalproof doctor` prints the mode in effect and warns when
210
+ `api-key` is chosen but the CLI's key variable is not set.
211
+
212
+ * **Isolation is a requirement, not an option.** A coding agent is not a model endpoint. Left
213
+ alone it loads the project's `CLAUDE.md` or `AGENTS.md`, your memory, plugins, MCP servers
214
+ and skills into the prompt, and it can run commands. A judge that has read your project's
215
+ instructions is not a judge. So every call runs in a fresh empty directory, with the
216
+ agent's own system prompt replaced by yours (or one neutral line), its tools, settings, MCP
217
+ servers and skills switched off, and no permission-bypass flag. `evalproof doctor` prints
218
+ the exact flags. The two CLIs do not isolate equally well:
219
+ * Claude Code is run with **no tools at all** (`--tools ''`).
220
+ * Codex runs in a read-only sandbox with its shell, plugins, apps, hooks, multi-agent mode,
221
+ sleep and image tools, bundled skills and your user config and rules switched off, but
222
+ codex-cli 0.167.2 has no flag that removes every tool. Still offered are a JavaScript
223
+ isolate (`exec`: no Node, no file system, no network, with a few built-in helpers such as
224
+ the current time, MCP resource listing and `apply_patch`), sub-agents that get the same
225
+ restricted tools, and a question to the user, which cannot be answered in exec mode. **Any
226
+ tool use fails the call**: if the output holds an item that is not a message, reasoning,
227
+ an error or a plan, evalproof raises `codex used a tool (<type>); isolation broken` and
228
+ discards the reply. A codex too old to know a flag evalproof sets exits with `Unknown
229
+ feature flag`, and evalproof says to upgrade it (it was tested with codex-cli 0.167.2).
230
+ * **No temperature, seed or token limit.** Neither CLI exposes one, so `temperature`, `seed`
231
+ and `max_tokens` are ignored. `think` maps to Codex's reasoning effort (`false` is `low`,
232
+ `true` is `medium`; `params.extra.reasoning_effort` sets it directly). A run is therefore
233
+ not repeatable the way a seeded local run is, so the run records the CLI, its version, the
234
+ isolation scheme and the model id that actually answered, and the cache keys on the CLI
235
+ version. To see how much a judge wobbles, score the same outputs twice with `--no-cache`, or
236
+ [calibrate](#concepts) it against labels.
237
+ * **A changed judge is not comparable.** `compare` and the gate refuse (exit 4) when the judge's
238
+ target, CLI version, isolation or resolved model differs between the two runs, because the
239
+ scores then come from a different instrument. Pass `--allow-judge-change` or set
240
+ `gate.allow_judge_change: true` to accept it. A changed system target or CLI version only
241
+ warns, since that is usually the change under test. Runs written before 0.4.0 carry none of
242
+ these keys and compare as before.
243
+ * **Latency and cost.** A call takes about 1.4 s on `claude-code:haiku` and about 4 s on
244
+ Codex, mostly start-up, so set `concurrency`. There is no API bill; the calls count against
245
+ your subscription's usage limits.
246
+ * **Terms.** OpenAI documents Codex CLI use on a ChatGPT plan, including automation.
247
+ Anthropic's terms allow a user to run the unmodified Claude Code binary on their own
248
+ subscription; its plan limits assume ordinary individual use, and it forbids third-party
249
+ products from routing other users' requests through subscription credentials. evalproof
250
+ never touches credentials: it runs your own `claude` binary, on your machine, for your own
251
+ evals. For CI or team-wide use, use an API key (`EVALPROOF_CLI_AUTH=api-key`, or
252
+ `api_key_env`). See Anthropic's
253
+ [legal and compliance page](https://code.claude.com/docs/en/legal-and-compliance). Nothing
254
+ uses a CLI unless a suite names a `claude-code:` or `codex:` target (or an experimental one,
255
+ below).
256
+
257
+ ### Experimental providers
258
+
259
+ `opencode:`, `cursor:` and `copilot:` follow the same rules as the two above: your own logged-in
260
+ binary, run as a subprocess in a fresh empty directory, its API-key variable removed from the
261
+ child's environment unless `api_key_env` names one, a bounded runtime, and the CLI, its version
262
+ and an isolation tag (`opencode/0`, `cursor/0`, `copilot/0`) in the run and in the cache key.
263
+ They are experimental because their isolation is less proven than Claude Code's and Codex's.
264
+ Each prints `evalproof: <name> provider is experimental` on stderr the first time it is used in
265
+ a process. Like the other two they ignore `temperature`, `seed` and `max_tokens`, and
266
+ `evalproof doctor` without a suite lists each of them whose binary is on `PATH`. What was
267
+ checked, and what was not:
268
+
269
+ * **`opencode:<provider>/<model>`** runs `opencode run --standalone --archived --format json`
270
+ with a custom agent that has every tool off and every permission denied, your system text as
271
+ that agent's prompt, and project config, the global config directory and the Claude Code
272
+ compatibility files switched off. The prompt is on stdin. *Verified* only against local
273
+ ollama models (`qwen3:8b`, a handful of calls): no tool event, a canary `AGENTS.md` in the
274
+ project not read, a canary `AGENTS.md` in the global config directory read by default and not
275
+ read with these settings, the parser and the error path. *Not verified:* any hosted model;
276
+ whether `~/.claude/CLAUDE.md` is read (that test was inconclusive); whether the empty
277
+ `OPENCODE_CONFIG_DIR` hides a login stored there; the token counts (input adds cache reads
278
+ and writes, output adds reasoning, summed per field name). OpenCode has no single login, so
279
+ `doctor` prints `auth: not checked`. Only `OPENCODE_API_KEY` is removed from the environment:
280
+ the key of the model's own provider (`ANTHROPIC_API_KEY` for `opencode:anthropic/...`) is
281
+ left, because naming that provider in the model id is choosing its key, and removing it would
282
+ break CI. `--archived` keeps a session out of OpenCode's lists but does not delete it, so
283
+ sessions accumulate in OpenCode's data directory (each run is a private `--standalone`
284
+ server in a temporary project); prune them yourself.
285
+ * **`cursor:<model>`** runs `cursor-agent -p --mode ask --trust` with an empty `--workspace`.
286
+ *Not verified:* Cursor was not logged in on the test machine, so no call succeeded. The flags
287
+ come from the CLI's help and documentation, and the parser from its source; it is lenient (a
288
+ JSON result object, an array of events, terminal noise around it) and an unexpected shape
289
+ fails with a message that says what was expected. The empty workspace is the only isolation:
290
+ no flag ignores rules files, user-level rules were not checked, and whether ask mode really
291
+ has no tools is untested. There is no system-prompt option, so your system text goes in front
292
+ of the prompt under a heading, and the prompt is an argument (nothing says the CLI reads
293
+ stdin), so it is visible in `ps` while the call runs and a prompt over 100,000 bytes is
294
+ refused. The key variable is `CURSOR_API_KEY`, passed through the environment, never as
295
+ `--api-key`. `doctor` reads `cursor-agent about` for the login state (`status` hung) and
296
+ prints no account.
297
+ * **`copilot:<model>`** runs `copilot -s --no-auto-update --no-ask-user --available-tools= -p`.
298
+ *Not verified:* Copilot CLI was not installed on the test machine; every flag comes from
299
+ GitHub's documentation. `--available-tools=` is an allow-list with nothing in it, the most
300
+ restrictive documented tool option; whether the CLI accepts an empty list was not tried. No
301
+ `--allow-*` flag is ever passed, so a tool that needs approval cannot get it. The reply is
302
+ plain text, so token counts are 0. `COPILOT_HOME` is not redirected, so user-level Copilot
303
+ instructions may still be read. System text and prompt travel as for Cursor. Only
304
+ `COPILOT_GITHUB_TOKEN` is removed; `GH_TOKEN` and `GITHUB_TOKEN` are left because `gh` and CI
305
+ set them for other reasons. `doctor` prints `auth: not checked`.
306
+
153
307
  ## Concepts
154
308
 
155
309
  **Suite** — one YAML file: a JSONL golden set, the system under test, scorers, an optional
@@ -174,7 +328,8 @@ gate: {metric: judge, max_drop: 0.03, alpha: 0.05, min_cases: 40, max_p95_latenc
174
328
 
175
329
  **Run** — `evalproof run suite.yaml` writes one self-describing JSON file: every case's
176
330
  output, latency, tokens and scores, plus the dataset hash, seed, Ollama version and model
177
- digests.
331
+ digests, and for a `claude-code` or `codex` target the CLI, its version and the model that
332
+ answered.
178
333
 
179
334
  **Scorers** — `exact_match`, `contains`, `regex` (accept/reject answer keys),
180
335
  `json_schema`, `numeric_tolerance`, `recall_at_k`, `mrr`, `ndcg`, and `llm_judge`: a strict
@@ -199,7 +354,7 @@ orders and reports order-dependent verdicts as ties.
199
354
  | 0 | pass — possibly with warnings (a big drop that is not significant; a significant drop within tolerance) |
200
355
  | 1 | quality regression: drop ≥ `max_drop` **and** p < `alpha` |
201
356
  | 3 | budget breach: candidate p95 latency or cost over the limit |
202
- | 4 | cannot gate: fewer than `min_cases` paired cases, or the runs used different data |
357
+ | 4 | cannot gate: fewer than `min_cases` paired cases, the runs used different data, or the judge changed (unless `--allow-judge-change`) |
203
358
  | 2 | usage or configuration error |
204
359
 
205
360
  The gate checks one metric by default. To gate several, repeat `--metric` or list them in
@@ -304,6 +459,7 @@ flowchart LR
304
459
  | Local Ollama by default, HTTP-only providers, content-addressed cache | Hosted APIs and vendor SDKs | Reproducible, free to re-run, tied to a model digest; no SDK dependency | [0002](docs/adr/0002-local-first-providers.md) |
305
460
  | Gate on "large AND significant", on paired cases | "Fail if the score dropped by X" | On a few dozen cases, X-point drops are mostly noise, and a noisy gate gets switched off | [0003](docs/adr/0003-gate-on-significance-not-delta.md) |
306
461
  | One metric by default; several with Holm correction | Gate every metric at α each | Testing many metrics at α = 0.05 each inflates false failures | 0003 |
462
+ | Run a coding-agent CLI as a provider only in isolation, and gate on its fingerprint | Pass the CLI's defaults through, or ignore its version | A CLI that reads your project's instructions is not a judge, and a silent upgrade changes the instrument | [0004](docs/adr/0004-agent-clis-as-providers.md) |
307
463
  | Pointwise verdicts for gating; pairwise as a diagnostic | Pairwise preference for gating | Measured: 42% of pairwise verdicts flipped with answer order, with a strong length preference | [MEASUREMENTS §6](MEASUREMENTS.md) |
308
464
  | numpy and the standard library for statistics | SciPy, statsmodels | Small enough to read and to test against hand-worked values | — |
309
465
 
@@ -313,7 +469,7 @@ The composite action runs `compare --gate`, appends the report to the job summar
313
469
  fails the job on exit codes 1, 3 or 4:
314
470
 
315
471
  ```yaml
316
- - uses: technobasant/evalproof@v0.3.0
472
+ - uses: technobasant/evalproof@v0.4.0
317
473
  with:
318
474
  baseline: eval/baseline.json # produced on main, e.g. as a build artifact
319
475
  candidate: eval/candidate.json # produced on this branch
@@ -324,6 +480,14 @@ fails the job on exit codes 1, 3 or 4:
324
480
  This repository's own CI runs the offline example on every commit and asserts that the
325
481
  gate returns 1 for the real regression and 0 for the noisy and refactored candidates.
326
482
 
483
+ If the judge is a Claude Code or Codex CLI, `evalproof init --ci api-key` writes a workflow that
484
+ installs the CLI at the version found at init time and runs `evals/suite.yaml` as it is. The run
485
+ step sets `EVALPROOF_CLI_AUTH: api-key` and the repository secret (`ANTHROPIC_API_KEY` for
486
+ Claude Code, `OPENAI_API_KEY` for Codex), so the CLI uses that key instead of a login. The
487
+ suite names no key and there is no second suite file; a suite with an `api_key_env` still uses
488
+ that variable. Keep the pinned CLI version equal to the one in your baseline run, because the
489
+ gate exits 4 when the judge's CLI version differs.
490
+
327
491
  ## How it relates to other tools
328
492
 
329
493
  [promptfoo](https://www.promptfoo.dev/docs/intro/) (open-source, local evals of prompts,
@@ -1,3 +1,3 @@
1
1
  """The package version: the single place it is written. pyproject.toml reads it at build time."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.4.0"