Skip to content

[Phase 2] Extend the harness to prompts/deep-review.md via an LLM-judge scorer #585

Closed

Description

@github-actions

Story

As a skill maintainer,
I want a held-out case set for deep-review plus an LLM-judge scoring adapter for its non-deterministic output,
so that proves the harness generalizes beyond a deterministic skill to a second high-traffic skill.

Acceptance Criteria

  1. skills/evals/deep-review/cases.jsonl exists with held-out cases capturing the expected risk classification / approve-vs-escalate decision and the key findings deep-review.md should surface.
  2. run-eval.sh supports a per-skill scorer mode — deterministic (triage) vs LLM-judge (deep-review) — selected by per-skill config, not hardcoded branching on skill name in the comparator core.
  3. The LLM-judge runs on a Haiku-tier model and emits a numeric per-case score; the judge prompt is itself a versioned file under skills/evals/ and is CODEOWNER-gated like the cases.
  4. Offline tests cover the judge-mode plumbing with a stubbed judge (no network).

Tasks / Subtasks

Dev Notes

  • deep-review.md outputs a findings array plus a decision/risk (not a single boolean), so deterministic equality cannot score it. The idea explicitly allows 'a deterministic check or a Haiku-tier LLM-judge' — use the judge here.
  • engine.sh's triage tier is already a Haiku-tier model (set_engine_config) — reuse that tier for the judge to keep cost low. Drive it through run_triage/run_agentic rather than a new invocation path.
  • Keep the comparator core generic: select scorer mode from per-skill config so adding a third skill later needs no edits to the core. The judge prompt is a skill-quality input, so version it and CODEOWNER-gate it exactly like cases (reward-hacking guard).
  • Cite the shared risk taxonomy (prompts/shared.md) when authoring expected classifications.

Project Structure Notes

Reuses the skills/evals/ tree and run-eval.sh from Phase 1; adds deep-review cases and a versioned judge prompt alongside the triage assets.

References

  • prompts/deep-review.md
  • prompts/shared.md
  • scripts/engine.sh#set_engine_config
  • scripts/engine.sh#run_agentic
  • skills/evals/run-eval.sh

Likely target surface

  • skills/evals/deep-review/cases.jsonl
  • skills/evals/judge.md
  • skills/evals/run-eval.sh
  • tests/test_skill_evals.bats

Story prepared by the BMAD Scrum Master (Bob) for epic #581. Status: ready-for-dev.

Activity

  1. don-petry commented on Jun 14, 2026

    @don-petry
    Collaborator

    Dev-Lead Implementation Plan

    Issue: #585 — [Phase 2] Extend the harness to prompts/deep-review.md via an LLM-judge scorer

    Scope

    Add a held-out deep-review eval set plus a config-driven LLM-judge scorer mode to the existing Phase-1 harness, so non-deterministic deep-review output is scored by a Haiku-tier judge selected per-skill (no skill-name branching in the comparator core).

    Path mapping (issue surface → actual repo)

    The issue's skills/evals/* paths map to the repo's real tree:

    • scorer: scripts/evals/run-eval.sh
    • cases: evals/deep-review/{dev,holdout}/cases.jsonl (both splits required by validate-cases.py)
    • judge prompt: evals/judge.md
    • tests: tests/test_skill_evals.bats

    Implementation Checklist

    • evals/deep-review/holdout/cases.jsonl + evals/deep-review/dev/cases.jsonl — held-out cases capturing risk classification, approve-vs-escalate decision, and key findings (grounded in deep-review.md + shared.md risk taxonomy). Disjoint ids across splits.
    • evals/judge.md — versioned, CODEOWNER-gated LLM-judge prompt emitting a numeric score.
    • evals/deep-review/scorer.json — per-skill config selecting llm-judge mode (+ judge prompt + pass threshold). Absent config defaults to deterministic, so triage is unchanged.
    • scripts/evals/run-eval.sh — read per-skill scorer.json; dispatch a generic scorer mode (deterministic vs llm-judge); add an LLM-judge adapter that builds the judge prompt and invokes the judge via EVAL_JUDGE_CMD (default run_triage, Haiku-tier). Per-case numeric score.
    • .github/CODEOWNERS — explicit owner-lock for evals/judge.md (reward-hacking guard, like cases).

    Tests to Write

    • Judge-mode all-pass: stubbed skill + stubbed judge returning high scores → score 1, exit 0.
    • Judge-mode fail: stubbed judge returning a sub-threshold score → case fails, exit 1.
    • Per-case numeric score present in judge-mode results.
    • Judge mode reads only holdout/; never writes the cases file.
    • Unparseable judge output → case failure.
    • Existing deterministic (triage) tests stay green (no skill-name branching regression).
    • validate-cases.py accepts the new deep-review split (committed-tree test).

    Test Command

    bats tests/test_skill_evals.bats tests/test_validate_cases.bats && python3 evals/validate-cases.py && shellcheck scripts/evals/run-eval.sh && bash .dev-lead/scripts/dev-lead-lint.sh

  2. don-petry commented on Jun 14, 2026

    @don-petry
    Collaborator

    Dev-Lead: Implementation Complete

    Plan Execution

    Path note

    The issue's "likely target surface" used skills/evals/*; the live tree is scripts/evals/run-eval.sh (scorer) and evals/<skill>/{dev,holdout}/cases.jsonl (cases) from Phase 1. The judge prompt lives at evals/judge.md. Implemented against the live paths.

    Test Results

    bats tests/test_skill_evals.bats
    1..16
    ok 1 scores a correct stub as all-pass (score 1, exit 0)
    ...
    ok 11 llm-judge mode: all-pass scores 1 (exit 0) with per-case numeric score
    ok 12 llm-judge mode: a sub-threshold score fails the case (exit 1)
    ok 13 llm-judge mode: unparseable judge output counts as a failure (score 0)
    ok 14 llm-judge mode: the judge prompt carries both the expected ref and the candidate
    ok 15 llm-judge mode: scorer never writes the held-out cases file
    ok 16 absent scorer.json defaults to deterministic mode (triage unchanged)
    
    python3 evals/validate-cases.py
    OK: 3 skill(s), 21 case(s) across dev+holdout, no cross-split id overlap.
    
    bash .dev-lead/scripts/dev-lead-lint.sh
      [lint] shellcheck on 54 file(s)...
      [lint] validating 4 agent profile(s)...
      [lint] all checks passed
    
    Full bats suite (20 files from lint.yml): 346 tests, 0 failures.
    End-to-end smoke (committed deep-review assets, stubbed skill+judge): 5/5 holdout cases scored, threshold gating correct, exit 0.
    

    Files Changed

    • scripts/evals/run-eval.sh: config-driven scorer mode; score_deterministic + score_llm_judge functions; EVAL_JUDGE_CMD injectable engine command; per-case numeric score; aggregate report shape unchanged (score = passed/total).
    • evals/deep-review/holdout/cases.jsonl, evals/deep-review/dev/cases.jsonl: new held-out + dev splits (disjoint ids).
    • evals/deep-review/scorer.json: selects llm-judge mode + judge prompt + pass threshold.
    • evals/judge.md: versioned LLM-judge rubric emitting {"score": <0..1>, "reason": ...}.
    • .github/CODEOWNERS: owner-lock /evals/judge.md.
    • tests/test_skill_evals.bats: 6 new tests covering judge-mode plumbing (stubbed, offline) + the deterministic-default guarantee.

    Notes

    • Scorer mode is selected purely from scorer.json; a third skill needs only its own config + cases — no edits to the comparator core.
    • Judge runs on the triage (Haiku) tier via run_triage to keep cost low, per the Dev Notes.
    • Unparseable judge output and out-of-range scores are handled (score 0 → fail; scores clamped to [0,1]).
    • Follow-up (out of scope here): deep-review.md writes its verdict to $OUTPUT_FILE rather than stdout, while the scorer (like the Phase-1 triage scorer) captures stdout. Real end-to-end deep-review scoring would need a small prompt/harness adaptation to emit the verdict on stdout; the offline judge plumbing (AC Optimize review: small-PR and incremental fast paths #4) is what this story scopes.
  3. added a commit that references this issue on Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    dev-leadFor dev-lead agent pickupinitiativeEpic / initiative tracking issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions