Add BC PR-Review engine arm with BCQuality ref override - #739
Conversation
Lands the faithful convergence arm (engine adapter + BCQuality fetch/filter + review.json transform) and wires it into the CLI as 'bcbench evaluate engine'. The adapter accepts optional bcquality_repo/bcquality_ref that are passed to the engine's fetch via BCQUALITY_REPO/BCQUALITY_REF env vars, so a CI run can review against a modified BCQuality branch/SHA without editing the engine.
…oggle Adds CODE_REVIEW_BCQUALITY_REF (default empty) to the existing 'Evaluation with GitHub Copilot' workflow. Empty keeps the current Copilot code-review; a branch that sets it runs the code-review through the BC PR-Review engine arm against that BCQuality ref. No new workflow or command surface for the user - flip one committed env on a branch.
…drop separate engine command
There was a problem hiding this comment.
Pull request overview
Adds a BC PR-review engine evaluation path and workflow integration.
Changes:
- Adds engine invocation and findings normalization.
- Routes code-review evaluations through the engine.
- Adds conditional engine checkout and execution in CI.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/bcbench/evaluate/codereview_engine.py |
Implements the engine adapter. |
src/bcbench/commands/evaluate.py |
Adds engine routing to evaluation CLI. |
.github/workflows/copilot-evaluation.yml |
Configures the engine evaluation arm. |
The engine arm adapter targeted a stale script contract and never ran: - Pass -RepoPath / -BCQualityRoot (both mandatory) instead of -Workspace; provision a disposable BCQuality checkout (BCQUALITY_REF, or a copy of BCQUALITY_ROOT minus .git) since the engine filter deletes files in place. - Read the engine's actual output _review-report.json (not the never-written al-code-review-findings.json), from the OutputDir we pass. - Normalize real findings (location.file, location.line/range, message) and map BCQuality severity blocker/major/minor/info to critical/high/medium/low, per the production engine map. - workflow: point --engine-scripts-dir at agents/ALReviewAgent/scripts and pin ENGINE_REF to a real tag (1.15.4); v1.0.5 did not exist.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (2)
src/bcbench/commands/evaluate.py:90
- This branch accepts
--engine-scripts-dirwith every evaluation category even though the engine only produces code-review output. For bug-fix, test-generation, or nl2al, the selected category pipeline will subsequently look for a patch or answer that the engine never creates. Reject non-code-review categories before running the pipeline.
if engine_scripts_dir:
context.category.pipeline.execute(
src/bcbench/evaluate/codereview_engine.py:219
- The tests cover only finding-field mapping; none exercise the new subprocess orchestration introduced here. Add mocked tests for BCQuality ref/repository selection, the base/patch commit passed to PowerShell, timeout/failure handling, and selection of the engine's normalized artifact so regressions do not require a live engine run to detect.
bcquality_root = _prepare_bcquality(output_dir)
base_ref = _commit_patched_worktree(repo_path)
started_at = time.monotonic()
_run_local_review(local_review_script, repo_path, bcquality_root, engine_output_dir, base_ref, model)
- Move adapter to bcbench/agent/engine.py (runner package convention) and export run_engine_review from bcbench.agent - Record reproducibility: engine_ref + bcquality_sha on ExperimentConfiguration (flow into bceval export) - Map engine _run-metrics.json prompt/completion tokens into AgentMetrics - Guard --engine-scripts-dir to --category code-review - Fix summarize agent label (engine arm no longer mislabeled GitHub Copilot CLI) via evaluate-job output - Install powershell-yaml on the engine arm so Get-BCQualityConfig does not throw - Support BCQUALITY_REPO fork override; clarify raw agent-report faithfulness in module docstring - Split engine tests into tests/test_engine.py with orchestration/version/metrics coverage
|
|
||
| logger.info(f"Cloning BCQuality @ {ref}") | ||
| clone = subprocess.run( | ||
| ["git", *auth, "clone", "--quiet", url, str(dest)], |
| except subprocess.TimeoutExpired as exc: | ||
| raise AgentTimeoutError(f"PR-review engine timed out after {_ENGINE_TIMEOUT_SECONDS}s") from exc |
Point ENGINE_REF at the floating 'latest' tag instead of a pinned 1.15.4 so the arm tracks the newest BC-ALAgents release automatically. The exact commit reviewed is still recorded per run via engine_ref, so results stay reproducible.
| # BCQuality emits blocker/major/minor/info; the engine and BC-Bench gold use | ||
| # Critical/High/Medium/Low. Mirror the production engine's map | ||
| # (Invoke-CopilotPRReview.ps1: blocker=Critical, major=High, minor=Medium, info=Low). | ||
| _SEVERITY_MAP = {"blocker": "critical", "major": "high", "minor": "medium", "info": "low"} |
| try: | ||
| result = subprocess.run(args, capture_output=True, text=True, timeout=_ENGINE_TIMEOUT_SECONDS, check=False) | ||
| except subprocess.TimeoutExpired as exc: |
| shutil.copytree(local, dest, ignore=shutil.ignore_patterns(".git")) | ||
| return dest, _git_head(Path(local)) |
…e tokens Default the code-review category to the pinned PR-Review engine arm via code_review_uses_engine() so activation lives in code, not the workflow (GitHub Actions only runs master workflows; tests run on feature branches). --engine-scripts-dir becomes an optional local scripts override; BCBENCH_CODE_REVIEW_AGENT=copilot opts out for A/B. Pin _DEFAULT_ENGINE_REF to a released tag (1.15.4) so BCQuality content is the only variable under test and runs stay reproducible; ENGINE_REF still overrides. Unify severity on the shared Severity.from_input table (minor -> Medium, matching the production engine's BCQualitySeverityMap floor) and delete engine.py's divergent _SEVERITY_MAP so every arm scores identically. Sanitize clone-only credentials (BCQUALITY_REPO_TOKEN/ENGINE_REPO_TOKEN) from the reviewer subprocess env; only GH_TOKEN auth remains. Document the arm as a BCQuality-iteration harness scoring the raw reviewer report.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
src/bcbench/commands/evaluate.py:86
- This makes every code-review invocation select the engine because
code_review_uses_engine()defaults toTrue, even whenBCQUALITY_REFis empty and--engine-scripts-dirwas not supplied. In the default workflow path the run therefore clones/runs the engine while the job labels and summarizes it as GitHub Copilot CLI, contradicting the PR's opt-in contract and corrupting baseline results. Make the engine selection explicit (for example, activate it when the scripts option is supplied or when an explicit selector is set) and keep the no-option default on Copilot; the helper, CLI routing, tests, and workflow condition need to use the same rule.
use_engine = category == EvaluationCategory.CODE_REVIEW and code_review_uses_engine()
src/bcbench/agent/engine.py:373
- If the engine times out,
_run_local_reviewraisesAgentTimeoutErrorwithout metrics or configuration.EvaluationPipeline.executecopies those exception fields into the saved timeout result, so this path loses the elapsed time plusengine_ref/bcquality_sha, preventing the failed run from being reproduced or attributed to the tested inputs. Attach the metadata before propagating the timeout, as the existing Copilot runner does.
started_at = time.monotonic()
_run_local_review(local_review_script, repo_path, bcquality_root, engine_output_dir, base_ref, model)
execution_time = time.monotonic() - started_at
| # Engine (microsoft/BC-ALAgents) ref to check out when the engine arm runs. | ||
| # "latest" is the floating tag that tracks the newest engine release; the exact | ||
| # commit reviewed is still captured per run via the recorded engine_ref. | ||
| ENGINE_REF: "latest" |
Testers set bcquality-ref (and bcquality-repo for a fork) at dispatch to review against their own BCQuality with zero yaml edits and no merge to main. Code-review now defaults to the engine arm, which self-clones the engine + BCQuality and self-installs powershell-yaml, so the checkout-engine and install-yaml steps are dropped and their config moves out of the hardcoded env block.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/bcbench/agent/engine.py:178
copytreeincludes uncommitted and untracked BCQuality changes, but the recordedbcquality_shaidentifies onlyHEAD. Distinct local policy contents can therefore receive the same experiment identity and be merged by summary grouping, making the reported configuration non-reproducible. Either require a clean checkout or record an identity derived from the copied content (or an explicit dirty marker).
local = os.environ.get("BCQUALITY_ROOT")
if local:
shutil.copytree(local, dest, ignore=shutil.ignore_patterns(".git"))
return dest, _git_head(Path(local))
| dispatching on this branch runs the engine without any workflow env. Set | ||
| BCBENCH_CODE_REVIEW_AGENT=copilot to run the plain Copilot reviewer instead (A/B). | ||
| """ | ||
| return os.environ.get("BCBENCH_CODE_REVIEW_AGENT", "engine").strip().lower() != "copilot" |
| if engine_scripts_dir is not None: | ||
| return engine_scripts_dir, _git_head(engine_scripts_dir) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
src/bcbench/agent/engine.py:74
- This makes the engine the default for every code-review run, contradicting the PR contract that the arm is opt-in and that existing Copilot reviews remain unchanged when it is off. A plain
bcbench evaluate copilot ... --category code-reviewnow clones two repositories and runs the engine unless users discover an undocumented opt-out environment variable, so baseline results silently change. Please make activation explicit (for example, a non-emptyBCQUALITY_REFor--engine-scripts-dir) and retain Copilot as the default.
return os.environ.get("BCBENCH_CODE_REVIEW_AGENT", "engine").strip().lower() != "copilot"
.github/workflows/copilot-evaluation.yml:119
- The workflow is missing the documented
category == code-review && BCQUALITY_REF != ''activation gate: this labels every code-review dispatch as the engine, including the default blank-ref run that the PR description says should remain Copilot. Gate both engine selection and this label oninputs.bcquality-ref != ''; otherwise default benchmark runs are silently reassigned to a different arm.
$label = if ('${{ inputs.category }}' -eq 'code-review') { 'BC PR-Review Engine' } else { 'GitHub Copilot CLI' }
"label=$label" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
src/bcbench/agent/engine.py:279
- Unlike the existing Copilot/Claude/BCal runners, this timeout exception carries neither
AgentMetricsnorExperimentConfiguration.EvaluationPipeline.executecopies those fields fromAgentTimeoutErrorinto the saved timeout result, so timed-out engine runs lose their execution time,custom_agent, resolved engine ref, and BCQuality SHA—precisely the metadata needed to attribute and reproduce the run. Catch/rethrow at the orchestration level with the elapsed metrics and already-resolved configuration attached.
except subprocess.TimeoutExpired as exc:
raise AgentTimeoutError(f"PR-review engine timed out after {_ENGINE_TIMEOUT_SECONDS}s") from exc
| # minor -> Medium matches the production engine's severity floor | ||
| # (Invoke-CopilotPRReview.ps1 $BCQualitySeverityMap); one table for every arm. | ||
| "minor": Severity.MEDIUM, |
A full run (test-run=false) previously always pushed to Braintrust/Kusto and the leaderboard. The new publish-results input (default true) decouples publishing from test-run: summarize treats mock as (test-run OR not publish-results), so a BCQuality author can run the full benchmark with publish-results=false and keep results in artifacts only, never touching the shared dashboard.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/bcbench/agent/engine.py:74
- The arm is described as opt-in, with blank
BCQUALITY_REFpreserving the existing Copilot review, but this default routes every code-review invocation through the engine. As a result, an unchangedbcbench evaluate copilot --category code-reviewnow clones two repositories and requires PowerShell instead of running Copilot directly; the workflow also labels every code-review run as engine-backed. Please either restore the documented opt-in condition consistently across the CLI/workflow or update the PR contract if this baseline replacement is intentional.
return os.environ.get("BCBENCH_CODE_REVIEW_AGENT", "engine").strip().lower() != "copilot"
src/bcbench/agent/engine.py:178
- For a git-backed
BCQUALITY_ROOT,copytreeincludes uncommitted edits butbcquality_sharecords onlyHEAD. The result can therefore claim a reproducible BCQuality commit even though different content was reviewed, which is especially likely when this override is used to test a local policy change. Detect and record dirty content (or require a clean checkout) rather than reporting the clean HEAD as the reviewed content.
local = os.environ.get("BCQUALITY_ROOT")
if local:
shutil.copytree(local, dest, ignore=shutil.ignore_patterns(".git"))
return dest, _git_head(Path(local))
What
Adds the BC PR-Review engine arm to BC-Bench's
code-reviewcategory: instead of BC-Bench's own review pipeline, this arm runs the engine's production reviewer (Invoke-LocalReview.ps1frommicrosoft/BC-ALAgents) against the patched worktree and scores its findings with BC-Bench's existingCodeReviewPipeline. This lets us benchmark the real engine + BCQuality policy head-to-head with the Copilot arm.The arm is opt-in and code-review only; with it off, nothing changes.
Changes
src/bcbench/evaluate/codereview_engine.py(new) —run_engine_review(...), the engine adapter:HEADas the diff base.BCQUALITY_ROOT(minus.git) if set, otherwise clonesmicrosoft/BCQualityatBCQUALITY_REF(defaultmain). A disposable copy is required because the engine's filter step deletes files in place, so it must never point at a live clone. The clone token is passed via an auth header (never in the remote URL).Invoke-LocalReview.ps1 -RepoPath -BaseRef -BCQualityRoot -OutputDir [-Model]._review-report.jsonfromOutputDirand normalizes each finding (location.file,location.line/range.start-line,range.end-line,message) into the flatreview.jsonthe scorer already understands.blocker→critical,major→high,minor→medium,info→low.src/bcbench/commands/evaluate.py— wires the arm into the existingbcbench evaluate copilotcommand via a new--engine-scripts-diroption (envENGINE_SCRIPTS_DIR). When set (code-review only), the pipeline routes throughrun_engine_reviewand labels the agent "BC PR-Review Engine"; otherwise the Copilot path is unchanged..github/workflows/copilot-evaluation.yml— activates the arm on CI whencategory == 'code-review'andBCQUALITY_REF != ''. It checks outmicrosoft/BC-ALAgentsatENGINE_REF(1.15.4) intoengine/and passes--engine-scripts-dir engine/agents/ALReviewAgent/scripts. Default (emptyBCQUALITY_REF) keeps the current Copilot code-review.tests/test_codereview.py— addsTestEngineFindingMapping(severity mapping,line/rangefallback, skipping incomplete findings, unknown-severity passthrough).How to run the engine arm
BCQUALITY_REF(branch/tag/SHA) in the workflow env and dispatch withcategory: code-review. This pins the exact BCQuality content the arm reviews with, without editing the engine.ENGINE_SCRIPTS_DIR=<...>/agents/ALReviewAgent/scripts bcbench evaluate copilot <entry> --category code-review [--engine-scripts-dir ...], optionally withBCQUALITY_ROOTto reuse a local checkout.Validation
_review-report.json(4 findings → 4review.jsoncomments, severities mapped correctly).pytest tests/test_codereview.py— 64 passed;ruffclean.Review note (open question)
In CI the run step exposes only
GH_TOKEN(=github.token). Ifmicrosoft/BCQualityis private, the adapter's clone (step 2 above) may need a token with cross-repo read — e.g.ENGINE_REPO_TOKEN/BCQUALITY_REPO_TOKENexported into that step. Today it falls back togithub.token; flagging in case an explicit token needs to be threaded through whenBCQUALITY_ROOTisn't provided.