Judge tests never reach a model or the shared queue - #491
Open
EdbertChan wants to merge 9 commits into
Open
Conversation
…: a shared test base redirects the judge's state directory and runners, and every judge-calling hook's tests use it instead of their own hand-rolled isolation. Review lane: behavior Safety invariant: Test support code only. No hook decision logic changes and the judge library keeps its runtime defaults. Effectiveness measurement: A new test asserts an enqueue inside the base class writes a job file under the temporary directory while the judge's default cache path stays empty. Slice rationale: The base class and its adopters, alone. The suite runner and the coverage gate are the next task because the lint keeps repo-root scripts in their own task. Architectural effect: Test isolation moves from per-file convention into a shared base class that every judge-calling hook's tests inherit. Goal: Make it impossible for a hook's tests to call a model or write into the shared queue. Motivation: Real model calls and shared-queue writes happened during a unit test run. Alternative considerations: Leaving isolation to each test file was rejected: that is the convention that failed. Making the judge refuse to run outside a temporary directory was rejected as a runtime change that would affect live sessions. Implementation details: Read engine/hooks/llm-judge/judge.py for the environment variable names for the state directory and the runner list and for the runner entry shape, then read engine/hooks/llm-judge/tests/test_judge.py and engine/hooks/wrong-check-reflect/tests/test_hooks.py for the isolation already in use. Create engine/hooks/llm-judge/testing.py exporting a unittest.TestCase subclass whose setUp creates a temporary directory, points the state directory at it, and sets the runner list to one local stub command printing a single JSON line; tearDown restores the prior environment and removes the directory. Point the judge-calling tests under engine/hooks/llm-judge/tests, engine/hooks/wrong-check-reflect/tests and engine/hooks/diu-stop/tests at that base, and add one test asserting the enqueue lands in the temporary directory and not in the default cache path. Non-goals: No hook decision logic, no judge runtime defaults, no phrase list changes, and no repo-root script changes in this task. Layer: domain Feature state: active Files: - engine/hooks/llm-judge/testing.py - engine/hooks/llm-judge/tests/test_judge.py - engine/hooks/wrong-check-reflect/tests/test_hooks.py - engine/hooks/diu-stop/tests/test_plain_words_check.py Change types: - engine/hooks/llm-judge/testing.py: create - engine/hooks/llm-judge/tests/test_judge.py: modify - engine/hooks/wrong-check-reflect/tests/test_hooks.py: modify - engine/hooks/diu-stop/tests/test_plain_words_check.py: modify Acceptance criteria: - `python3 -m unittest discover -s engine/hooks/llm-judge/tests && python3 -m unittest discover -s engine/hooks/diu-stop/tests` exits 0. - `python3 scripts/check_no_new_comments.py --base origin/main` exits 0. Exit code: 0 Invoker-Finalize-Id: 839172f2-58a6-40f2-87fa-46c6eec5aa42
…the judge-calling hooks' test suites pass under the shared base. Review lane: proof Safety invariant: Verification is read-only and changes no file. Effectiveness measurement: The unittest runs are the measurement. Slice rationale: One proof for the base class and its adopters. Architectural effect: None; verification only. Goal: Prove the isolated suites pass. Motivation: Running them is the only evidence the base works. Alternative considerations: The whole suite was rejected as slower without adding evidence for this claim. Implementation details: Run the three judge-calling hooks' suites. Non-goals: No edits. Layer: app_regression Feature state: active Acceptance criteria: - Exits 0 only when all three suites pass. Exit code: 0 Invoker-Finalize-Id: f561c0e4-2e8b-4fef-b066-e6ce7e40c648
…iew claim: the suite runner isolates the judge's state directory and runners for the whole run, and the hook-coverage gate fails a judge-importing hook whose tests do not use the shared base. Review lane: policy Safety invariant: Tooling only. The runner sets environment variables for its own run and the gate only reads files; no hook or library code changes. Effectiveness measurement: Running the gate against a judge-importing hook whose tests lack the base fails and names that hook; the gate passes on the repository once the previous task has landed. Slice rationale: The two repo-root scripts, kept out of the previous task because the lint refuses policy files beside product files. Architectural effect: Isolation gains a second layer at the suite boundary, and the coverage gate gains one requirement. Goal: Make the suite safe even for a test that forgets the base class, and make the omission visible. Motivation: The convention was omitted once already, silently. Alternative considerations: Relying only on the base class was rejected: a new test file can skip it. Putting the check in CI only was rejected: the failure happened on a developer machine. Implementation details: In scripts/run_all_tests.sh, create a temporary directory and export the judge's state-directory and runner-list variables for the whole run, using the same names engine/hooks/llm-judge/judge.py reads. In scripts/check_hook_test_coverage.py, add a requirement that any hook whose non-test Python imports the judge has at least one test file referencing the shared base, and keep every existing requirement. Non-goals: No hook or library changes, no new gate file, and no CI workflow edits. Layer: transport Feature state: active Files: - scripts/run_all_tests.sh - scripts/check_hook_test_coverage.py Change types: - scripts/run_all_tests.sh: modify - scripts/check_hook_test_coverage.py: modify Acceptance criteria: - `python3 scripts/check_hook_test_coverage.py` exits 0. - `python3 scripts/check_no_new_comments.py --base origin/main` exits 0. Exit code: 0
… widened hook-coverage gate passes on this repository. Review lane: proof Safety invariant: Verification is read-only and changes no file. Effectiveness measurement: The gate's exit code is the measurement. Slice rationale: One proof for the gate change. Architectural effect: None; verification only. Goal: Prove the gate passes with its new requirement. Motivation: A gate that fails on its own repository is not landable. Alternative considerations: Running it for one hook only was rejected: the new requirement applies to every judge-importing hook. Implementation details: Run the hook-coverage gate over every hook. Non-goals: No edits. Layer: app_regression Feature state: active Acceptance criteria: - Exits 0 only when the gate passes for every hook. Exit code: 0 Invoker-Finalize-Id: 4a591ddd-22fe-4980-92a7-8600c3829a0b
….t0.a-a72292053-c8ebc9db
…no ephemeral handoff files remain in the worktree. Review lane: cleanup Safety invariant: The scrub script only checks for known handoff artifact names and never touches source or tests. Effectiveness measurement: The script exits non-zero if any handoff artifact remains. Slice rationale: Required terminal scrub for every implementation workflow. Architectural effect: None; hygiene only. Goal: Leave the branch free of handoff artifacts. Motivation: Handoff files must not reach the PR. Alternative considerations: Manual cleanup was rejected as non-deterministic. Implementation details: Run scripts/scrub-handoff-artifacts.sh. Non-goals: No product edits. Layer: app_regression Feature state: active Acceptance criteria: - `bash scripts/scrub-handoff-artifacts.sh` exits 0. Exit code: 0 Invoker-Finalize-Id: 06a0b20e-8df4-4247-a41c-990186826c56
…abec587ad-e41fdc0f — Review claim: no ephemeral handoff files remain in the worktree. Review lane: cleanup Safety invariant: The scrub script only checks for known handoff artifact names and never touches source or tests. Effectiveness measurement: The script exits non-zero if any handoff artifact remains. Slice rationale: Required terminal scrub for every implementation workflow. Architectural effect: None; hygiene only. Goal: Leave the branch free of handoff artifacts. Motivation: Handoff files must not reach the PR. Alternative considerations: Manual cleanup was rejected as non-deterministic. Implementation details: Run scripts/scrub-handoff-artifacts.sh. Non-goals: No product edits. Layer: app_regression Feature state: active Acceptance criteria: - `bash scripts/scrub-handoff-artifacts.sh` exits 0.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_3409cad3-0950-49d9-a987-a560a2d3754f) |
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
Judge-calling hook tests now use a shared base that redirects state to a temporary directory and replaces model runners with a local stub.
The full test runner applies the same isolation, while the coverage gate detects judge-importing hooks whose tests omit the shared base.
This prevents unit tests from making model calls or writing jobs into the user's shared judge queue.
Review Claim
Judge tests are isolated by construction and cannot reach a model or the shared queue.
Review Lane
behavior
Review Unit
engine-runtime
Safety Invariant
Test-only and gate-only. No hook decision code changes, and the judge library's runtime behavior outside tests remains untouched.
Slice Rationale
The shared test base, suite-wide isolation, and coverage requirement land together because each closes a different path to the same unsafe test behavior.
Non-goals
Architecture
Test isolation moves from a per-file convention to shared enforcement at two boundaries.
Before
graph TD A["Judge-calling tests"] --> B["Per-file isolation convention"] B --> C["Judge state and runners"] D["Suite runner"] --> CAfter
graph TD A["Judge-calling tests"] --> B["Shared test base"] B --> C["Temporary state and local stub runner"] D["Suite runner"] --> C E["Coverage gate"] --> F["Requires shared base reference"] A --> FTest Plan
Test Plan
python3 -m unittest discover -s engine/hooks/llm-judge/tests && python3 -m unittest discover -s engine/hooks/wrong-check-reflect/tests && python3 -m unittest discover -s engine/hooks/diu-stop/tests— 60, 16, and 104 tests passed; each runner reportedOK.python3 scripts/check_hook_test_coverage.py—check_hook_test_coverage: OK (36 hook(s) checked).bash scripts/scrub-handoff-artifacts.sh—scrub-handoff-artifacts-ok.python3 scripts/check_no_new_comments.py --base origin/main—ok no new comments.python3 engine/skills/make-pr/scripts/preflight.py --base origin/main—ok preflight passed.Revert Plan
Revert Plan
git revert <merge-commit-sha>.Note
Low Risk
Test infrastructure and coverage gates only; production hook and judge paths are unchanged.
Overview
Introduces a shared
JudgeTestCasebase inengine/hooks/llm-judge/testing.pyso judge-related tests always use a temporary state directory and a local stub runner instead of real model backends or the user’s default judge queue.Hook and llm-judge test suites are refactored to inherit that base (replacing ad hoc
patch.dict/ duplicate temp dirs).test_judge.pyadds a regression thatenqueueonly writes under the test state dir, and default-runner-order coverage clearsRUNNERS_ENVexplicitly.scripts/run_all_tests.shexports the same isolated state + stub runners for the full unittest discover run.scripts/check_hook_test_coverage.pyfails hooks that importjudgebut whose tests never referenceJudgeTestCase.No changes to hook decision logic or judge runtime behavior outside tests.
Reviewed by Cursor Bugbot for commit 1e6d9be. Bugbot is set up for automated code reviews on this repo. Configure here.