Epic: #839 · Story: #844 · Builds on: #961 (capture/emit) + #952 (extractor/harness)
Context
PR #961 added the pilot capture/emit logic to scripts/engine.sh + scripts/review-one-pr.sh (gated on the env LSP_PILOT_ENABLED), but only touched the scripts — not .github/workflows/pr-review.yml. Two gaps leave the feature inert / incomplete in real CI. This issue closes both so a production pr-review can emit a comparable lsp-off (A) and lsp-on (B) lsp_pilot_run record pair.
Gap 1 — the capture env var is never plumbed
_lsp_pilot_active() (engine.sh) and the trap in review-one-pr.sh gate on the env LSP_PILOT_ENABLED=true, but pr-review.yml only references the repo variable vars.LSP_PILOT_ENABLED (to gate the setup-lsp-pilot step, lines 378/387). vars.* are not automatically env vars, and neither the job env: block nor setup-lsp-pilot.sh exports LSP_PILOT_ENABLED to GITHUB_ENV. Result: with the repo var on, the LSP gets wired but no transcript is captured and no record is emitted (the unit tests pass only because they set the env directly).
Gap 2 — capture and LSP wiring can't be decoupled
Both the capture and the setup-lsp-pilot step key off the same vars.LSP_PILOT_ENABLED, and lpe_variant() returns lsp-on whenever REVIEW_MCP_CONFIG is wired. So a production run can only ever produce an lsp-on (B) record — there is no way to produce the lsp-off (A) control leg (capture on, LSP wiring off). The comparison needs both legs from the same pipeline.
Acceptance criteria
- Plumb the capture gate. The review job in
pr-review.yml must export LSP_PILOT_ENABLED as an env var to the step(s) that run the engine / review-one-pr.sh, so _lsp_pilot_active() actually sees it. When capture is enabled and a review runs, a kind:"lsp_pilot_run" record is emitted to TOKEN_LOG_FILE (already uploaded as the token-usage-<run_id> artifact).
- Decouple capture from LSP wiring. Add an independent control so a run can enable capture/emit WITHOUT wiring the LSP MCP (→
lsp-off A leg) and another that enables capture WITH LSP wiring (→ lsp-on B leg). Suggested shape: a workflow_dispatch input (with a matching repo-var default), e.g. lsp_pilot_variant: off | on | none — on runs setup-lsp-pilot (wires REVIEW_MCP_CONFIG) + capture; off runs capture only (skips setup-lsp-pilot, no REVIEW_MCP_CONFIG); none/unset = today's behaviour. Preserve the existing vars.LSP_PILOT_ENABLED meaning for anyone already using it.
- Both legs join. Dispatching the same PR twice (variant
off, then on) must yield two lsp_pilot_run records with the same pr key and variants lsp-off / lsp-on, that scripts/lsp_pilot_compare.sh renders as an off-vs-on table.
- Off-pilot unchanged. With neither the variable nor the input set,
pr-review.yml and the engine behave byte-for-byte as before — no stream capture, no records, no extra flags. The 5 consumer repos must be unaffected.
- Thin-caller forwarding. If a new dispatch input is added, forward it through
pr-review-trigger.yml (the ring-0 trigger stub) within its allowed-input contract so the A/B can be driven on this repo's own PRs. Respect the thin-caller rules in AGENTS.md.
- Tests + hygiene. Coverage for the new plumbing/decoupling;
shellcheck --severity=warning -x scripts/*.sh clean; pr-review.yml validates as YAML; existing test_engine_lsp_pilot / test_lsp_pilot_emit / test_lsp_pilot_measure suites stay green. Preserve all action/SHA pinning.
Out of scope
Done when
Dispatching the ring-0 pr-review on one PR with the pilot variant off then on drops two joinable lsp_pilot_run records (lsp-off + lsp-on) into their token-usage artifacts, and scripts/lsp_pilot_compare.sh renders a real off-vs-on table — no synthetic data and no manual env hacks.
Epic: #839 · Story: #844 · Builds on: #961 (capture/emit) + #952 (extractor/harness)
Context
PR #961 added the pilot capture/emit logic to
scripts/engine.sh+scripts/review-one-pr.sh(gated on the envLSP_PILOT_ENABLED), but only touched the scripts — not.github/workflows/pr-review.yml. Two gaps leave the feature inert / incomplete in real CI. This issue closes both so a production pr-review can emit a comparable lsp-off (A) and lsp-on (B)lsp_pilot_runrecord pair.Gap 1 — the capture env var is never plumbed
_lsp_pilot_active()(engine.sh) and the trap inreview-one-pr.shgate on the envLSP_PILOT_ENABLED=true, butpr-review.ymlonly references the repo variablevars.LSP_PILOT_ENABLED(to gate thesetup-lsp-pilotstep, lines 378/387).vars.*are not automatically env vars, and neither the jobenv:block norsetup-lsp-pilot.shexportsLSP_PILOT_ENABLEDtoGITHUB_ENV. Result: with the repo var on, the LSP gets wired but no transcript is captured and no record is emitted (the unit tests pass only because they set the env directly).Gap 2 — capture and LSP wiring can't be decoupled
Both the capture and the
setup-lsp-pilotstep key off the samevars.LSP_PILOT_ENABLED, andlpe_variant()returnslsp-onwheneverREVIEW_MCP_CONFIGis wired. So a production run can only ever produce anlsp-on(B) record — there is no way to produce thelsp-off(A) control leg (capture on, LSP wiring off). The comparison needs both legs from the same pipeline.Acceptance criteria
pr-review.ymlmust exportLSP_PILOT_ENABLEDas an env var to the step(s) that run the engine /review-one-pr.sh, so_lsp_pilot_active()actually sees it. When capture is enabled and a review runs, akind:"lsp_pilot_run"record is emitted toTOKEN_LOG_FILE(already uploaded as thetoken-usage-<run_id>artifact).lsp-offA leg) and another that enables capture WITH LSP wiring (→lsp-onB leg). Suggested shape: aworkflow_dispatchinput (with a matching repo-var default), e.g.lsp_pilot_variant: off | on | none—onrunssetup-lsp-pilot(wiresREVIEW_MCP_CONFIG) + capture;offruns capture only (skipssetup-lsp-pilot, noREVIEW_MCP_CONFIG);none/unset = today's behaviour. Preserve the existingvars.LSP_PILOT_ENABLEDmeaning for anyone already using it.off, thenon) must yield twolsp_pilot_runrecords with the sameprkey and variantslsp-off/lsp-on, thatscripts/lsp_pilot_compare.shrenders as an off-vs-on table.pr-review.ymland the engine behave byte-for-byte as before — no stream capture, no records, no extra flags. The 5 consumer repos must be unaffected.pr-review-trigger.yml(the ring-0 trigger stub) within its allowed-input contract so the A/B can be driven on this repo's own PRs. Respect the thin-caller rules in AGENTS.md.shellcheck --severity=warning -x scripts/*.shclean;pr-review.ymlvalidates as YAML; existingtest_engine_lsp_pilot/test_lsp_pilot_emit/test_lsp_pilot_measuresuites stay green. Preserve all action/SHA pinning.Out of scope
lsp_pilot_measure.sh/lsp_pilot_compare.shrecord schema.Done when
Dispatching the ring-0 pr-review on one PR with the pilot variant
offthenondrops two joinablelsp_pilot_runrecords (lsp-off + lsp-on) into theirtoken-usageartifacts, andscripts/lsp_pilot_compare.shrenders a real off-vs-on table — no synthetic data and no manual env hacks.