Feat: add unified vera judge command - #195
Draft
luca-belli wants to merge 2 commits into
Draft
Conversation
Mirrors `vera generate`: same five-step spine (register, run, resolve_configs, _from_cli/_from_config, _execute), and `resolve_input` is reused unchanged -- the per-command parameterization of the config-or-flags rule needed no modification to serve a second command. `ResolvedTarget`'s rubric fields, validated since #191 and unused until now, needed no new manifest code. Three changes were not a mirror. `RunConfig` becomes multi-section: `generation` and `judging` are both optional with at least one required. `to_dict` omits absent sections rather than emitting null, because its output doubles as input config and each command rejects top-level keys it does not own -- a null section would make `generate --print` emit something `generate` itself refuses. The existing byte-identical generate round-trip is covered by a test. `generation_persona_sets` becomes `targets_from_config`, owning only the two rules both commands share: the `target: "all"` fan-out and the target/explicit-fields mutual exclusion. Projecting a resolved target onto the fields a command needs stays in the command module, which avoids two near-copies of the exclusivity logic. Provider parameters return to the CLI as `--judge-params`, plus `--user-params` and `--chatbot-params` for generate, which silently lost the capability in #191 -- `ModelSpec.from_shorthand` always produced empty params, leaving `--config` the only way to set them. CHANGELOG v1.2 documents `-jep` in its migration instructions, so this was a live regression. Parameters are supplied per role, as the legacy scripts accepted, and stored per model, so `--print` shows what each model will use. Judge-specific behavior, per the decisions in #193: - `--target all` is rejected until Phase 4 adds `evaluations/<target>/`; until then N rubric runs would share one output folder and be distinguishable only by timestamp. - `--conversations` takes exactly one folder; judge separately and combine with `vera pool`. - No `--resume` and no single-conversation mode; both stay in legacy `judge.py`. - `--sample` doubles as the debug cap, limiting conversations for judge and personas per file for generate, so `InvocationConfig` stays uniform. `JudgingConfig` rejects two inputs the domain cannot honor: repeated model names, which would silently collapse because judge models are keyed by name, and differing per-model provider parameters, because `run_judging` takes one parameter dict for the whole run. Breaking change, noted in CHANGELOG.md and architecture.md: `vera judge` drops legacy `judge.py`'s fallback of writing evaluations to `evaluations/` relative to the working directory when the input is a flat transcript folder rather than a generation run. It errors and requires `-o/--output`. Old flat folders remain readable with an explicit `-o`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Twenty tests mirroring tests/unit/test_vera_cli.py, focused on the decisions that are easy to regress rather than on restating the parser. Behavior parity: `--target` and `--rubric` resolve to identical configs, and output defaults beside the conversation run. The judge-specific rejections each get a test, since every one of them exists to prevent silently wrong output: `--target all`, more than one conversations folder, repeated judge model names, differing per-model provider parameters, and a flat folder with no `-o` -- plus the matching case showing an explicit `-o` still works against a flat folder. Two tests guard the multi-section schema. One asserts a judging run omits the `generation` key entirely rather than emitting null, since `to_dict` output doubles as input config. The other round-trips a resolved run through `VERA_RUN_CONFIG` and compares against the original object. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
luca-belli
marked this pull request as draft
August 14, 2026 00:31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the second unified CLI feature:
vera judge.Stacked on #194, which exposed the resolved-value judging entry point this
calls. Design decisions it implements were settled in #193.
Review guide
vera_cli/judge.pyis the same five-step spine asvera_cli/generate.py:register→run→resolve_configs→_from_cli/_from_config→_execute.Nothing below step 3 reads an
argparse.Namespace; nothing above it touches thejudging domain.
Both resolve to byte-identical configs, exactly as
--target/--personasdo forgenerate.resolve_inputwas reused unchanged. The per-command parameterization of theconfig-or-flags rule needed no modification to serve a second command, and
ResolvedTarget's rubric fields — validated since #191, unused until now —needed no new manifest code.
Three things that were not a mirror
RunConfigis multi-section.generationandjudgingare both optional,at least one required.
to_dictomits absent sections rather than emittingnull: its output doubles as input config, and each command rejects top-level keys
it does not own, so a null section would make
generate --printemit somethinggenerateitself refuses. Covered by two tests, including a round-trip throughVERA_RUN_CONFIG.generation_persona_sets→targets_from_config. It now owns only the tworules both commands share — the
target: "all"fan-out and target/explicit-fieldmutual exclusion. Projecting a resolved target onto each command's fields stays
in the command module, avoiding two near-copies of the exclusivity logic.
Provider parameters return to the CLI.
--judge-params, plus--user-paramsand--chatbot-paramsforgenerate, which silently lost thecapability in #191 —
ModelSpec.from_shorthandalways produced empty params,leaving
--configthe only route. CHANGELOG v1.2 documents-jepin itspublished migration instructions, so this was a live regression. Parameters are
supplied per role, as the legacy scripts accepted, and stored per model, so
--printshows what each model will actually use.Judge-specific behavior (per #193)
--target allrejected until Phase 4 addsevaluations/<target>/; untilthen N rubric runs share one output folder, distinguishable only by timestamp.
--conversationstakes exactly one folder — judge separately, combine withvera pool. List-shaped per AD-20.--resume, no single-conversation mode. Both stay in legacyjudge.py.--sampleis the shared debug cap — conversations for judge, personas perfile for generate — so
InvocationConfigstays uniform rather than growing aper-command cap.
-c. Judging is decoupled from chatbot selection, and-cmeant--conversationin legacyjudge.py.Two ceilings found in the domain
JudgingConfigrejects inputsrun_judgingcannot honor, rather than acceptingthem and silently doing something else:
would collapse.
run_judgingtakes one parameterdict for the whole run. The schema is already shaped for per-model params, so
this check can be deleted once the domain supports them.
Breaking change
vera judgedrops legacyjudge.py's fallback of writing evaluations toevaluations/relative to the working directory when the input is a flattranscript folder rather than a generation run. It errors and requires
-o/--output.The default for a generation run is unchanged —
<conversation run>/evaluations/,beside the transcripts. Old flat folders stay readable with an explicit
-o,satisfying the read-old-data guarantee in
architecture.md. Dropped because thefallback detached evaluations from the conversations that produced them, and
because a bare relative path resolves against the working directory on the CLI
but against the repository root in a config.
Recorded in
CHANGELOG.mdunder[Unreleased] → Breaking / migrationand as anAcknowledged compatibility breakin architecture.md's Phase 1 row.Validation
E501s indistribute_files.pyandjudge/response_models.py, verified against a stashed baseline)vera.py,vera_cli/,utils/config_schema.py,judge/run.py,judge.py: 0 errors--target/--rubricparity,--target allrejected, flatfolder errors without
-oand works with it, judge config round-trips, andgenerate's round-trip is still byte-identical with nojudgingkey — theregression risk the multi-section schema introduced
🤖 Generated with Claude Code