From 7517ea0cd47634a9c7380e3204931b0e0c878b76 Mon Sep 17 00:00:00 2001 From: Marcin Polewski Date: Sat, 31 Oct 2026 02:33:47 -0600 Subject: [PATCH 1/4] Run a cost-capped smoke eval on main when a prompt, charter or model changes Adds the Smoke eval workflow, --max-cost, --subset and --precision off to evals/run.ts, and evals/smoke.ts to compare a run with evals/baseline-smoke.json and scan evals/out for secrets before it's uploaded. Refs #52 --- .github/workflows/smoke-eval.yml | 76 +++++++++++++ CHANGELOG.md | 1 + README.md | 11 +- evals/cases.ts | 9 ++ evals/compare.ts | 5 +- evals/run.ts | 99 +++++++++++++---- evals/smoke.ts | 150 +++++++++++++++++++++++++ test/evals.test.ts | 9 ++ test/smoke-eval.test.ts | 182 +++++++++++++++++++++++++++++++ 9 files changed, 519 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/smoke-eval.yml create mode 100644 evals/smoke.ts create mode 100644 test/smoke-eval.test.ts diff --git a/.github/workflows/smoke-eval.yml b/.github/workflows/smoke-eval.yml new file mode 100644 index 0000000..8a2acf6 --- /dev/null +++ b/.github/workflows/smoke-eval.yml @@ -0,0 +1,76 @@ +# A small, cost-capped eval on main whenever a prompt, a charter, a model or the guidance changes, so a regression shows +# before a release reaches the repositories that use the action. The full eval stays on demand (README, Evals). +# +# It runs the smoke subset in evals/cases.ts with the production guidance, stops and fails past $8, and compares the run +# with evals/baseline-smoke.json in the job summary: it fails when caught drops by more than 2 or the must-not case is +# flagged. With no baseline yet, the run is uploaded as the proposed one and the job passes with a notice. +# +# It uses the organization's Bedrock key in the code-review environment, which only the default branch can use. Never +# add a pull_request trigger: the key would then run code from any pull request. +name: Smoke eval + +on: + push: + branches: [main] + paths: + - 'prompts/**' + - 'reviewers/**' + - 'src/config.ts' + - 'guidance/**' + workflow_dispatch: + +permissions: {} + +# A run costs money, so a newer push waits for the running one instead of cancelling it. +concurrency: + group: smoke-eval + cancel-in-progress: false + +jobs: + smoke-eval: + name: Smoke eval + runs-on: ubuntu-latest + environment: code-review + timeout-minutes: 90 + permissions: + contents: read + # The eval reads the issues a case's pull request closes. + issues: read + env: + LABEL: smoke-${{ github.sha }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 24 + - run: npm ci --ignore-scripts --no-audit --no-fund + + # The precision judge's Opus calls are left out: the gate reads caught and the must-not case only. + - name: Run the smoke subset + env: + AWS_BEARER_TOKEN_BEDROCK: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }} + GH_TOKEN: ${{ github.token }} + run: node evals/run.ts --subset smoke --guidance full --precision off --max-cost 8 --label "$LABEL" + + - name: Compare with the baseline + run: node evals/smoke.ts check "evals/out/$LABEL.json" + + # Artifacts on a public repository are public: nothing is uploaded unless the scan passes. + - name: Scan the outputs for secrets + id: scan + if: ${{ !cancelled() }} + env: + AWS_BEARER_TOKEN_BEDROCK: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }} + GH_TOKEN: ${{ github.token }} + run: node evals/smoke.ts scan evals/out + + - name: Upload the outputs + if: ${{ !cancelled() && steps.scan.outcome == 'success' }} + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: smoke-eval + path: evals/out/ + retention-days: 30 + if-no-files-found: ignore diff --git a/CHANGELOG.md b/CHANGELOG.md index 49cf6ab..acd43f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added +- A cost-capped smoke eval: the Smoke eval workflow runs 4 eval cases, among them the must-not case and a large diff, on every push to `main` that changes a prompt, a charter, `src/config.ts` or the guidance, and by hand, in the `code-review` environment and never on a pull request. It compares the run with `evals/baseline-smoke.json` in the job summary, fails when caught drops by more than 2 or the must-not case is flagged, and uploads `evals/out` once a secret scan passes. Until a baseline is committed, it passes with a notice and uploads the run as the proposed one. `node evals/run.ts` gains `--max-cost `, which stops and fails the run once it costs more, `--subset ` for a named set of cases, and `--precision off`; `--only` now fails on a case that doesn't exist ([#52](https://github.com/NCodeGroup/code-review/issues/52)). - Evals measure precision: each posted blocker and should-fix that matches no expected concern is judged `real`, `not-real` or `unsure` by Opus reading the checkout, with verdicts cached in `evals/verdicts.json`. Each output also reports findings per case (mean, max) and the review's cost per case. `npm run eval:compare` compares two runs, marking recall differences of 2 or less as noise, and `npm run eval:confirm` adds confirmed verdicts to `cases.json` as expected or must-not-flag concerns ([#51](https://github.com/NCodeGroup/code-review/issues/51)). - A per-review cost ceiling guards against a runaway review: $8 plus $4 for each part of the diff, overridable with `CODE_REVIEW_MAX_RUN_USD`. Reviewer calls not started once it is reached are skipped and shown as `skipped: run budget` in the check run, synthesis still runs on what was found, and the summary warns that the review is incomplete ([#50](https://github.com/NCodeGroup/code-review/issues/50)). - A verification stage, off by default: with `CODE_REVIEW_VERIFICATION=on`, a Sonnet call with the checkout proves or refutes each merged blocker and should-fix, a file's findings at a time at up to $0.50 a call. Confirmed findings get the code path in their evidence, refuted ones are dropped and listed with the reason under "Refuted by verification" in the check run and in the trace, and ones it can't settle become questions one severity lower. The summary names the refuted findings, and verification turns a request for changes with nothing serious left into a comment, never an approval. Nits aren't verified, and the calls show as `verification` in the cost table. It stays off until the eval shows a gain, and the eval report now counts posted blockers and should-fixes and refuted findings ([#41](https://github.com/NCodeGroup/code-review/issues/41)). diff --git a/README.md b/README.md index 14d8cd6..3b4075e 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,16 @@ The cases come from review comments the learned guidance has read too, so the ev **Precision.** Every posted blocker and should-fix that matches no expected concern goes to an Opus judge, which reads the case's checkout, locked down like the reviewers, with the finding passed as data, and answers `real`, `not-real` or `unsure`: is it a real defect the maintainer would act on? A finding that raises a must-not-flag concern counts as not real without a call. Precision is (matched + real) ÷ all posted blockers and should-fixes, with `unsure` counted apart. Verdicts are cached by case and finding fingerprint (file, severity and the title's words) in `evals/verdicts.json`, so a rerun judges only findings it hasn't seen; commit it with `cases.json` so every run is judged the same way. Each `evals/out/