feat(pr-review): shadow-mode quality gate (#185 Phase 2)#238
feat(pr-review): shadow-mode quality gate (#185 Phase 2)#238cbeaulieu-gt merged 4 commits intomainfrom
Conversation
Adds a structured-marker parser to pr-review/action.yml that runs alongside the existing prose-regex gate. Parses the HTML-comment claude-pr-review-summary-v1 block emitted by the Phase 1 persona, derives a verdict from the per-severity counts, and posts an advisory claude-pr-review/quality-gate-shadow commit status with an agree/disagree/marker_missing label. Ships: - pr-review/lib/parse-marker.sh — sourceable parser, single source of truth - pr-review/tests/marker-cases/ — 5-fixture corpus including PR #222's pre-marker review body (the #223 false-negative regression case) - pr-review/tests/run-marker-cases.sh — test driver, modeled on claude-command-router/tests/run-cases.sh - .github/workflows/test.yml — runs the marker corpus on every PR - .github/workflows/marker-emission-aggregate.yml — weekly cron that surfaces marker_missing events as a deduped GitHub issue, providing the data source for verifying the Phase 3 cutover criterion The shadow status is advisory only and must NOT be added to branch protection. The existing claude-pr-review/quality-gate remains authoritative until #185 Phase 3 cutover. Refs #185 #183 #223 #227 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- SC2016 (3 blocks): suppress at run: block level via shellcheck disable comment (repo convention for jq queries inside single quotes) - SC2034: remove unused `state` variable - SC2129: combine consecutive >> $GITHUB_ENV redirects into a single block-redirect No functional change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Claude finished @cbeaulieu-gt's task in 1m 49s —— View job PR Review Complete ✅I've reviewed the incremental changes (c645b45 → 983179e) — these are high-quality cleanup changes addressing actionlint/shellcheck warnings in the aggregator workflow. Todo List
SummaryChanges reviewed: The commit addresses three categories of linter warnings:
Code Quality ✅
Security ✅
Performance ✅
Test Coverage ✅
Documentation ✅
Recommendation: APPROVE ✅These changes successfully address linter warnings without altering workflow behavior. The cleanup improves code quality and follows repository conventions. No issues found. |
The YAML-comment-above-run approach did not suppress shellcheck warnings in workflow files (only works for composite actions). Moving the disable comment to the first line of the embedded bash script body is the correct form for workflow run: blocks. No other change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Claude finished @cbeaulieu-gt's task in 1m 16s —— View job PR Review Complete ✅I've reviewed the latest incremental change (983179e → 81bd020). Todo List
SummaryCommit reviewed: Changes: This commit corrects the placement of shellcheck disable directives at three locations (lines 42, 127, 159). The Fix ✅Before (incorrect): - name: Step name
# shellcheck disable=SC2016
run: |
script contentAfter (correct): - name: Step name
run: |
# shellcheck disable=SC2016
script contentWhy This MattersThe previous placement had shellcheck directives as YAML comments (outside the script body). Shellcheck processes the actual shell script content passed to Moving them inside the script body (first line after Code Quality ✅
Security ✅
Performance ✅
Test Coverage ✅
Documentation ✅
Recommendation: APPROVE ✅This commit correctly fixes the shellcheck directive placement from the previous commit. The directives are now positioned where shellcheck can actually process them (inside the script body, not as YAML comments). Good catch and clean fix. |
SC2016 requires per-statement disable directives when the affected lines are not the first statement in the script — a single disable at the top only covers the immediately following statement (set -uo pipefail), not subsequent ones. Add # shellcheck disable=SC2016 immediately before each multi-line jq/printf command that contains $ in single-quoted strings. No functional change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Claude finished @cbeaulieu-gt's task in 1m 18s —— View job PR Review Complete ✅I've reviewed the latest incremental change (81bd020 → 21e87ca). Todo List
SummaryCommit reviewed: Changes: This commit adds shellcheck disable directives at three additional locations (lines 51, 167, 178) — completing the lint cleanup started in the previous two commits. The Pattern ✅Each new directive follows the correct pattern established in commit run: |
# shellcheck disable=SC2016
COMMAND=$(statement with ${{ }} or other SC2016 trigger)Three additions:
Why These Are CorrectSC2016 warns about
This follows CLAUDE.md's documented convention for GitHub Actions workflows. Code Quality ✅
Security ✅
Performance ✅
Test Coverage ✅
Documentation ✅
Recommendation: APPROVE ✅This commit completes the shellcheck lint cleanup for |
The shadow quality-gate (claude-pr-review/quality-gate-shadow) has been posting state=error / marker_missing on every PR review since glitchwerks/github-actions#238 merged. Root cause is producer/consumer skew: v2.3.0's overlay container predates #238 and does not emit the <!-- claude-pr-review-summary-v1 marker, but the shadow gate (which consumes that marker) runs anyway because the composite action is fetched via floating pr-review@v2. Bumping the reusable workflow pin to v2.4.0 brings in the post-#238 overlay digest whose persona emits the marker. Fixes #296 Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

Summary
The prose-regex quality gate (
claude-pr-review/quality-gate) is fragile: it can miss blocking findings when the persona phrases them in ways the regex doesn't catch. Issue #223 documents the concrete regression: PR #222's review contained### Critical Issues Found 🚨and**two critical inconsistencies**in lowercase — the gate emitted a falsesuccess.Phase 1 (#228/#229) patched the persona to emit a structured JSON marker block at the end of every review. This PR adds the shadow-mode gate that parses that marker and runs alongside the existing prose-regex gate — advisory, never blocking merge.
What ships
pr-review/lib/parse-marker.sh— sourceable bash parser, single source of truth. Extracts the<!-- claude-pr-review-summary-v1block from a review body, validates the JSON schema (closed schema:schemaVersion+findings.{critical,high,medium,low}), and emitsoutcome|critical|high|medium|lowon stdout. Four outcomes:marker_missing,marker_invalid,clean,blocking.pr-review/tests/marker-cases/— 5-fixture corpus:pr-222-critical-issues-found.md— the actual PR docs: refresh consumer-facing README for Phase 5+ container-pinned workflows #222 review body (verbatim from the GitHub API). Pre-Phase-1 body, no marker block → expectedmarker_missing. This is the quality-gate reports success on review with explicit "two critical" language and "### Critical Issues Found" heading #223 regression reproducer.clean-review.md— all-zero findings,Verdict: APPROVE, valid marker → expectedclean|0|0|0|0.mixed-severity.md— one finding at each severity level, valid marker with counts → expectedblocking|1|1|1|1.marker-missing.md— pre-Phase-1 era body with no marker block → expectedmarker_missing.marker-malformed.md— marker block present butfindings.critical: "two"(string instead of int) → expectedmarker_invalid. Defect documented inline in the fixture.pr-review/tests/run-marker-cases.sh— test driver modeled onclaude-command-router/tests/run-cases.sh.set -uo pipefail(not-e), accumulates failures, printssummary: N/M passed, exits 1 on any failure..github/workflows/test.yml— adds a second jobmarker-casesthat runsbash ./pr-review/tests/run-marker-cases.shon every PR and push to main. Mirrors the existingrouter-casesjob structure..github/workflows/marker-emission-aggregate.yml— weekly cron (Mondays 07:00 UTC) +workflow_dispatch. Queries the last 7 days of merged PRs, checks each head SHA forquality-gate-shadowstatuses, counts outcomes (agree:clean, agree:blocking, disagree:*, marker_missing, marker_invalid), writes a step summary table, and opens/updates a deduped issue titledQuality-gate shadow: marker_missing events detectedif any marker_missing events occurred. Always exits 0 — observability only.pr-review/action.yml— adds one new stepQuality gate — structured marker (advisory shadow)after the existing authoritative gate step. The step sourcesparse-marker.shvia$GITHUB_ACTION_PATH, re-runs the prose-regex (same pattern as the authoritative gate), derives an agree/disagree/marker_missing label, postsclaude-pr-review/quality-gate-shadowassuccess/failure/error, and writes a step-summary markdown table. The existingQuality gate — post claude-pr-review/quality-gate statusstep is unchanged.Test plan
actionlint .github/workflows/test.yml .github/workflows/marker-emission-aggregate.yml— clean locallybash ./pr-review/tests/run-marker-cases.shpasses withsummary: 5/5 passedin CI (themarker-casesjob in this PR's check list)claude-pr-review/quality-gateandclaude-pr-review/quality-gate-shadowin the PR checks listagree:clean,disagree:prose=blocking/marker=clean,marker_missing)claude-pr-review/quality-gate-shadow— verify in repo Settings → Branches → branch protection rules after mergeAdvisory-only guarantee
The shadow status
claude-pr-review/quality-gate-shadowMUST NOT be added to branch protection. The authoritative gate isclaude-pr-review/quality-gate. The shadow period exists to accumulate evidence for the Phase 3 cutover criterion (locked decision #5: ≥20 reviews across ≥5 PRs, zero disagreement, zero marker_missing).Refs #185 #183 #223 #227
🤖 Generated by Claude Code on behalf of @cbeaulieu-gt