Skip to content

refactor: extract testable prompt helpers from the two judges#149

Merged
jithin23-kv merged 2 commits into
masterfrom
refactor-judge-module-srp
Jul 1, 2026
Merged

refactor: extract testable prompt helpers from the two judges#149
jithin23-kv merged 2 commits into
masterfrom
refactor-judge-module-srp

Conversation

@prasanth-nair-kv

@prasanth-nair-kv prasanth-nair-kv commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

What & why

judgeResponse (~115 lines) and judgeToolResponse each bundled prompt
assembly with the LLM call, so their subtle logic was only reachable through a
live judge run. This extracts two behavior-preserving, unit-testable helpers
(from the PR4 max-review, findings #6/#7):

  • pairTurnsForJudge (evaluators/judge.ts) — the role-alternation pairing
    window: filter to user/assistant, step-by-2, warn-and-fallback to a single
    synthetic turn.
  • buildMcpJudgePrompt + McpJudgePromptInput (run/judge.ts) — the MCP
    judge user-prompt assembly (description-scan branch, prior-turns rendering,
    evidence-rule ordering). judgeToolResponse is now just build → call → parse.

Behavior-preserving

Pure mechanical moves — the prompt arrays were copied verbatim. Guards:

  • an exact-string snapshot pins the MCP prompt byte-for-byte;
  • focused unit tests for the pairing helper (clean pairing, fallback on
    undefined/empty/single, trailing-unpaired-turn dropped, _opfor_* arg stripping,
    each prompt branch);
  • the runAll.smoke + orchestrator.equivalence integration guards stay green.

judgeToolResponse's args type moved from an inline object to
McpJudgePromptInput & { model } — structurally identical, and typecheck confirms
no caller breaks.

Verification

typecheck 0 errors · lint 0 issues · full suite 82 tests, 0 fail (3 skips) · build
(incl. esbuild browser bundle) passes.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved judge prompt generation for tool responses and conversation history, making turn pairing and prompt assembly more consistent.
    • Added clearer handling for tool errors, scan/description mode, and prior turns in judge prompts.
  • Bug Fixes

    • Falls back safely when conversation history cannot form valid turn pairs.
    • Preserves the strongest judge instructions and removes internal-only fields from the rendered prompt.
  • Tests

    • Added coverage for prompt rendering and turn pairing across common and edge-case scenarios.

judgeResponse (~115 lines) and judgeToolResponse each bundled prompt assembly
with the LLM call, so the subtle bits were only reachable through a live judge
run. Extract two behavior-preserving, unit-testable helpers:

- pairTurnsForJudge (evaluators/judge.ts): the role-alternation pairing window
  (filter to user/assistant, step-by-2, warn-and-fallback to a single turn).
- buildMcpJudgePrompt + McpJudgePromptInput (run/judge.ts): the MCP judge
  user-prompt assembly (description-scan branch, prior-turns rendering, evidence
  ordering). judgeToolResponse is now build -> call -> parse.

Pure mechanical moves — prompt arrays copied verbatim. An exact-string snapshot
pins the MCP prompt byte-for-byte; the pairing helper gets focused unit tests;
the runAll.smoke and orchestrator.equivalence integration guards stay green.
judgeToolResponse's args type went from an inline object to
McpJudgePromptInput & { model }, which is structurally identical (typecheck
confirms no caller breaks).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jithin23-kv, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b9b89d4c-a028-4b75-b195-abfb6948499e

📥 Commits

Reviewing files that changed from the base of the PR and between eb2284f and 593cf5d.

📒 Files selected for processing (3)
  • core/src/evaluators/judge.ts
  • core/src/run/judge.ts
  • core/tests/pairTurnsForJudge.test.ts

Walkthrough

Refactors judge prompt construction across two modules: extracts transcript-to-turn pairing into an exported pairTurnsForJudge() helper in the evaluator judge module, and extracts MCP judge prompt assembly into an exported buildMcpJudgePrompt() function with a new McpJudgePromptInput type in the run judge module. Adds corresponding unit tests.

Changes

Judge Prompt Construction Refactor

Layer / File(s) Summary
Transcript pairing helper
core/src/evaluators/judge.ts, core/tests/pairTurnsForJudge.test.ts
Adds exported pairTurnsForJudge() that pairs user/assistant transcript entries or falls back to a synthetic turn, wires it into judgeResponse() replacing inline pairing logic, and adds unit tests covering fallback and pairing scenarios.
MCP judge prompt builder
core/src/run/judge.ts, core/tests/buildMcpJudgePrompt.test.ts
Adds exported McpJudgePromptInput interface and buildMcpJudgePrompt() function that assembles the MCP judge prompt (evaluator, attack, tool response/error, description-scan modes), updates judgeToolResponse() to use it, and adds unit tests covering minimal input, error/response rendering, description-scan mode, judge hints, evaluator instructions, prior turns, and internal field stripping.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant JudgeToolResponse as judgeToolResponse
  participant PromptBuilder as buildMcpJudgePrompt
  participant LLM as chatCompletionJsonContent

  JudgeToolResponse->>PromptBuilder: McpJudgePromptInput args
  PromptBuilder->>PromptBuilder: assemble evaluator/attack/prior-turn blocks
  PromptBuilder->>PromptBuilder: select response block (description-scan / error / response)
  PromptBuilder-->>JudgeToolResponse: assembled prompt string
  JudgeToolResponse->>LLM: user prompt string
  LLM-->>JudgeToolResponse: JudgeResult
Loading

Suggested reviewers: achuvyas-kv, arunSunnyKVS, jithin23-kv

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly reflects the main refactor: extracting testable prompt helpers from both judge flows.
Description check ✅ Passed It covers the problem, solution, changes, and verification, though it uses custom headings instead of the template's exact section names.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor-judge-module-srp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/src/run/judge.ts (1)

115-124: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use Zod to validate the parsed MCP tool response instead of casting.

JSON.parse(args.toolResponse) as {...} directly types external MCP response data. The try/catch prevents a crash, but the cast bypasses validation of content/text shapes.

♻️ Proposed fix using Zod
+const DescriptionResponseSchema = z.object({
+  content: z.array(z.object({ text: z.string().optional() })).optional(),
+});
+
 if (isDescriptionScan) {
     const descriptionText = (() => {
       try {
-        const parsed = JSON.parse(args.toolResponse) as { content?: Array<{ text?: string }> };
-        return parsed.content?.[0]?.text ?? args.toolResponse;
+        const parsed = DescriptionResponseSchema.parse(JSON.parse(args.toolResponse));
+        return parsed.content?.[0]?.text ?? args.toolResponse;
       } catch {
         return args.toolResponse;
       }
     })();

As per coding guidelines, "Use Zod for all external input, including config files, LLM responses, and MCP responses; never JSON.parse directly into a typed variable."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/src/run/judge.ts` around lines 115 - 124, The description-scan path in
judge.ts currently parses args.toolResponse and casts it to a typed shape, which
bypasses validation of external MCP data. Update the isDescriptionScan branch to
validate the parsed response with Zod instead of using a TypeScript cast, and
only read content/text after successful schema validation; keep the existing
fallback to args.toolResponse for invalid or non-JSON input. Use the existing
descriptionText logic in judge.ts as the place to introduce the Zod schema and
parsing flow.

Source: Coding guidelines

🧹 Nitpick comments (2)
core/tests/pairTurnsForJudge.test.ts (1)

44-65: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a test for the partial-misalignment silent-drop case.

Current tests cover clean pairing, total fallback (empty/undefined/single), and a trailing unpaired user. None exercise an interleaved misalignment (e.g. [user, assistant, assistant, user, assistant]) where a valid trailing pair is dropped due to the fixed step-by-2 alignment — see the corresponding comment on pairTurnsForJudge in core/src/evaluators/judge.ts. Adding this case would make the silent-drop behavior explicit and catch regressions/fixes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/tests/pairTurnsForJudge.test.ts` around lines 44 - 65, Add a focused
test in pairTurnsForJudge.test.ts for the partial-misalignment silent-drop case:
use pairTurnsForJudge with an interleaved sequence like
user/assistant/assistant/user/assistant and assert the current fixed step-by-2
behavior drops the valid trailing pair. Place it alongside the existing
pairTurnsForJudge scenarios so the regression is explicit, and make sure the
test name clearly references the misalignment/drop behavior.
core/src/evaluators/judge.ts (1)

60-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: extract shared turn type to avoid repeating the inline shape.

{ user: string; assistant: string } is repeated for both the fallback parameter and the return type. A small type JudgeTurn = { user: string; assistant: string } alias would reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/src/evaluators/judge.ts` around lines 60 - 63, The `pairTurnsForJudge`
signature repeats the same `{ user: string; assistant: string }` shape for both
`fallback` and the return type. Introduce a shared `JudgeTurn` type alias in
`judge.ts`, then update `pairTurnsForJudge` to use it for the `fallback`
parameter and returned array so the shape is defined once.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/src/evaluators/judge.ts`:
- Around line 60-84: pairTurnsForJudge is silently skipping valid user/assistant
turns when the filtered history is not strictly alternating. Update the pairing
logic in pairTurnsForJudge to resync after mismatches instead of advancing by
fixed steps, so a later valid user/assistant pair is still captured. Also add
drop detection based on pairable consumption (not just turns.length === 0) and
emit a warning when any non-trivial history was partially dropped, keeping the
fallback behavior only for true no-pair cases.

---

Outside diff comments:
In `@core/src/run/judge.ts`:
- Around line 115-124: The description-scan path in judge.ts currently parses
args.toolResponse and casts it to a typed shape, which bypasses validation of
external MCP data. Update the isDescriptionScan branch to validate the parsed
response with Zod instead of using a TypeScript cast, and only read content/text
after successful schema validation; keep the existing fallback to
args.toolResponse for invalid or non-JSON input. Use the existing
descriptionText logic in judge.ts as the place to introduce the Zod schema and
parsing flow.

---

Nitpick comments:
In `@core/src/evaluators/judge.ts`:
- Around line 60-63: The `pairTurnsForJudge` signature repeats the same `{ user:
string; assistant: string }` shape for both `fallback` and the return type.
Introduce a shared `JudgeTurn` type alias in `judge.ts`, then update
`pairTurnsForJudge` to use it for the `fallback` parameter and returned array so
the shape is defined once.

In `@core/tests/pairTurnsForJudge.test.ts`:
- Around line 44-65: Add a focused test in pairTurnsForJudge.test.ts for the
partial-misalignment silent-drop case: use pairTurnsForJudge with an interleaved
sequence like user/assistant/assistant/user/assistant and assert the current
fixed step-by-2 behavior drops the valid trailing pair. Place it alongside the
existing pairTurnsForJudge scenarios so the regression is explicit, and make
sure the test name clearly references the misalignment/drop behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a3641e9-99ee-472c-a4f1-3d228e79a8eb

📥 Commits

Reviewing files that changed from the base of the PR and between df2df80 and eb2284f.

📒 Files selected for processing (4)
  • core/src/evaluators/judge.ts
  • core/src/run/judge.ts
  • core/tests/buildMcpJudgePrompt.test.ts
  • core/tests/pairTurnsForJudge.test.ts

Comment thread core/src/evaluators/judge.ts
pairTurnsForJudge: replace the fixed step-by-2 loop with a greedy pass that
resyncs by one entry on a role mismatch, so a valid user/assistant pair after
a misaligned entry is captured instead of silently dropped. Warn on genuine
misalignment (distinct from a benign trailing odd turn) so an incomplete
transcript cannot reach the judge unnoticed.

buildMcpJudgePrompt: validate the description-scan response with a Zod schema
instead of a JSON.parse cast, per the "Zod for all external input" convention.
Falls back to the raw response for non-JSON/invalid input.

Extract the repeated { user, assistant } shape into a JudgeTurn alias, and add
a regression test covering resync recovery on a misaligned transcript.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jithin23-kv

Copy link
Copy Markdown
Collaborator

Addressed the review findings:

  • Turn pairingpairTurnsForJudge now pairs greedily and resyncs by one entry on a role mismatch, so a valid user/assistant pair after a misaligned entry is captured instead of silently dropped. Warns on genuine misalignment.
  • MCP scan input — the description-scan response is now validated with a Zod schema instead of a JSON.parse cast (per the "Zod for all external input" convention), falling back to the raw response on invalid/non-JSON input.
  • Cleanup — extracted the repeated { user, assistant } shape into a JudgeTurn alias.
  • Test — added a regression test covering resync recovery on a misaligned transcript.

@jithin23-kv
jithin23-kv merged commit 89b7e6a into master Jul 1, 2026
7 of 9 checks passed
@jithin23-kv
jithin23-kv deleted the refactor-judge-module-srp branch July 1, 2026 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants