fix: reset gateBlockCount when deliverableType changes (DLD-3465) #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: AI Review | |
| on: | |
| pull_request: | |
| branches: [master] | |
| types: [opened, synchronize, reopened] | |
| concurrency: | |
| group: ai-review-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| review: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| statuses: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.pull_request.head.ref }} | |
| fetch-depth: 0 | |
| - name: Detect whether AI review is required | |
| id: changes | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| changed="$(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}")" | |
| printf '%s\n' "$changed" | |
| if printf '%s\n' "$changed" | grep -Eq '(^|/)(\.github/workflows/|package\.json|pnpm-workspace\.yaml|Dockerfile|scripts/|server/|ui/|cli/|packages/|tests/|tools/|agents/|skills/|deploy/|docker/|.*\.(ts|tsx|js|jsx|mjs|cjs|json|yml|yaml|sh))$'; then | |
| echo "run_ai_review=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "run_ai_review=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up Node.js | |
| if: steps.changes.outputs.run_ai_review == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Check for [ai-fix] marker | |
| if: steps.changes.outputs.run_ai_review == 'true' | |
| run: | | |
| HEAD_MSG="$(git log -1 --pretty=%s)" | |
| if echo "$HEAD_MSG" | grep -qF '[ai-fix]'; then | |
| echo "SKIP_REMEDIATION=true" >> "$GITHUB_ENV" | |
| else | |
| echo "SKIP_REMEDIATION=false" >> "$GITHUB_ENV" | |
| fi | |
| - name: Post pending status | |
| if: steps.changes.outputs.run_ai_review == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh api repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }} \ | |
| -X POST \ | |
| -f state=pending \ | |
| -f context="ai-review/verdict" \ | |
| -f description="AI review in progress..." \ | |
| -f target_url="${{ github.event.pull_request.html_url }}" | |
| - name: Fetch PR metadata | |
| if: steps.changes.outputs.run_ai_review == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: | | |
| PR_JSON="$(gh pr view ${{ github.event.pull_request.number }} --json title,body,url,changedFiles,additions,deletions)" | |
| echo "PR_TITLE=$(echo "$PR_JSON" | jq -r '.title')" >> "$GITHUB_ENV" | |
| echo "PR_URL=$(echo "$PR_JSON" | jq -r '.url')" >> "$GITHUB_ENV" | |
| echo "PR_FILES_CHANGED=$(echo "$PR_JSON" | jq -r '.changedFiles')" >> "$GITHUB_ENV" | |
| echo "PR_ADDITIONS=$(echo "$PR_JSON" | jq -r '.additions')" >> "$GITHUB_ENV" | |
| echo "PR_DELETIONS=$(echo "$PR_JSON" | jq -r '.deletions')" >> "$GITHUB_ENV" | |
| echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> "$GITHUB_ENV" | |
| echo "$PR_JSON" | jq -r '.body // ""' > /tmp/pr-body.txt | |
| gh pr diff ${{ github.event.pull_request.number }} > /tmp/pr-diff.txt || true | |
| # Fetch inline review comments and top-level reviews for context | |
| gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments --paginate \ | |
| | jq -s --arg head_sha "$HEAD_SHA" 'add | map(select((.commit_id // "") == $head_sha or (.original_commit_id // "") == $head_sha) | {path, line, side, body, author: .user.login})' > /tmp/pr-review-comments.json 2>/dev/null || echo '[]' > /tmp/pr-review-comments.json | |
| gh pr view ${{ github.event.pull_request.number }} --json reviews \ | |
| | jq '[.reviews[] | {author: .author.login, state, submittedAt, body, commit: (.commit.oid // "")}]' > /tmp/pr-reviews.json 2>/dev/null || echo '[]' > /tmp/pr-reviews.json | |
| - name: Run AI review | |
| if: steps.changes.outputs.run_ai_review == 'true' | |
| env: | |
| MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} | |
| PR_BODY_FILE: /tmp/pr-body.txt | |
| PR_DIFF_FILE: /tmp/pr-diff.txt | |
| PR_REVIEW_COMMENTS_FILE: /tmp/pr-review-comments.json | |
| PR_REVIEWS_FILE: /tmp/pr-reviews.json | |
| run: | | |
| if [ -z "${MINIMAX_API_KEY:-}" ]; then | |
| echo "HAS_MINIMAX_KEY=false" >> "$GITHUB_ENV" | |
| else | |
| echo "HAS_MINIMAX_KEY=true" >> "$GITHUB_ENV" | |
| fi | |
| if [ -z "${MINIMAX_API_KEY:-}" ]; then | |
| echo '{"verdict":"HIGH_RISK","summary":"MINIMAX_API_KEY not configured","findings":[{"severity":"note","message":"AI review secret missing; manual review required.","file":"","line":0}]}' > /tmp/review-result.json | |
| else | |
| node scripts/ai-review.mjs > /tmp/review-result.json || true | |
| fi | |
| if [ ! -s /tmp/review-result.json ]; then | |
| echo '{"verdict":"FAIL","summary":"Review script failed","findings":[]}' > /tmp/review-result.json | |
| fi | |
| echo "VERDICT=$(jq -r '.verdict // "FAIL"' /tmp/review-result.json)" >> "$GITHUB_ENV" | |
| echo "SUMMARY=$(jq -r '.summary // "Review failed"' /tmp/review-result.json)" >> "$GITHUB_ENV" | |
| - name: Apply AI-suggested version bump | |
| id: version_bump | |
| if: | | |
| steps.changes.outputs.run_ai_review == 'true' && | |
| env.SKIP_REMEDIATION == 'false' && | |
| env.HAS_MINIMAX_KEY == 'true' && | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| BOT_PAT: ${{ secrets.BOT_PAT }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| set -euo pipefail | |
| node scripts/apply-version-bump.mjs /tmp/review-result.json > /tmp/version-bump-result.json || true | |
| if [ -s /tmp/version-bump-result.json ]; then | |
| cat /tmp/version-bump-result.json | |
| STATUS="$(jq -r '.status // "error"' /tmp/version-bump-result.json)" | |
| echo "VERSION_BUMP_STATUS=$STATUS" >> "$GITHUB_ENV" | |
| if [ "$STATUS" = "applied" ]; then | |
| echo "applied=true" >> "$GITHUB_OUTPUT" | |
| # GITHUB_TOKEN-triggered events do NOT fire downstream workflows | |
| # (GitHub anti-recursion safeguard). Use BOT_PAT if configured so | |
| # close+reopen triggers a fresh pull_request:reopened event on | |
| # the bumped SHA; otherwise emit a warning for manual handling. | |
| if [ -n "${BOT_PAT:-}" ]; then | |
| GH_TOKEN="$BOT_PAT" gh pr close "$PR_NUMBER" --repo "${{ github.repository }}" || true | |
| GH_TOKEN="$BOT_PAT" gh pr reopen "$PR_NUMBER" --repo "${{ github.repository }}" || true | |
| else | |
| echo "::warning::BOT_PAT secret not configured — bumped commit was pushed but new workflow runs will not fire automatically. Push, comment, or close+reopen the PR manually to trigger checks, or add a BOT_PAT secret to automate this." | |
| fi | |
| else | |
| echo "applied=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| else | |
| echo "VERSION_BUMP_STATUS=error" >> "$GITHUB_ENV" | |
| echo "applied=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Determine workflow auto-remediation scope | |
| if: steps.changes.outputs.run_ai_review == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| CHANGED_FILES=$(gh pr diff ${{ github.event.pull_request.number }} --name-only | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| FINDING_FILES=$(jq -c '[.findings[]? | select(.file and (.file | length > 0)) | .file] | unique' /tmp/review-result.json) | |
| FINDINGS_COUNT=$(jq -r '(.findings // []) | length' /tmp/review-result.json) | |
| HAS_CRITICAL=$(jq -r 'any(.findings[]?; .severity == "critical")' /tmp/review-result.json) | |
| echo "CHANGED_FILES=$CHANGED_FILES" >> "$GITHUB_ENV" | |
| echo "FINDING_FILES=$FINDING_FILES" >> "$GITHUB_ENV" | |
| echo "FINDINGS_COUNT=$FINDINGS_COUNT" >> "$GITHUB_ENV" | |
| echo "HAS_CRITICAL=$HAS_CRITICAL" >> "$GITHUB_ENV" | |
| SAME_REPO=false | |
| if [ "${{ github.event.pull_request.head.repo.full_name }}" = "${{ github.repository }}" ]; then | |
| SAME_REPO=true | |
| fi | |
| AUTO_REMEDIATE_SCOPE=$( | |
| CHANGED_FILES="$CHANGED_FILES" FINDING_FILES="$FINDING_FILES" node -e 'const changed = JSON.parse(process.env.CHANGED_FILES || "[]"); const findingFiles = JSON.parse(process.env.FINDING_FILES || "[]"); const allow = (file) => typeof file === "string" && file.startsWith(".github/"); const allChangedAllowed = changed.length > 0 && changed.every(allow); const allFindingsAllowed = findingFiles.every(allow); process.stdout.write(allChangedAllowed && allFindingsAllowed ? "true" : "false");' | |
| ) | |
| echo "SAME_REPO=$SAME_REPO" >> "$GITHUB_ENV" | |
| echo "AUTO_REMEDIATE_SCOPE=$AUTO_REMEDIATE_SCOPE" >> "$GITHUB_ENV" | |
| - name: Auto-remediate workflow/config findings | |
| id: workflow_remediate | |
| if: | | |
| steps.changes.outputs.run_ai_review == 'true' && | |
| steps.version_bump.outputs.applied != 'true' && | |
| env.SAME_REPO == 'true' && | |
| env.AUTO_REMEDIATE_SCOPE == 'true' && | |
| env.HAS_CRITICAL != 'true' && | |
| env.FINDINGS_COUNT != '0' && | |
| env.SKIP_REMEDIATION == 'false' && | |
| env.HAS_MINIMAX_KEY == 'true' | |
| env: | |
| MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| export AI_REVIEW_FINDINGS="$(jq -c '.findings // []' /tmp/review-result.json)" | |
| export AI_REMEDIATE_PATHS="${CHANGED_FILES}" | |
| export PR_BODY_FILE=/tmp/pr-body.txt | |
| node scripts/ai-review-remediate.mjs > /tmp/workflow-remediation-result.json || true | |
| if [ ! -s /tmp/workflow-remediation-result.json ]; then | |
| echo "REMEDIATION_STATUS=error" >> "$GITHUB_ENV" | |
| echo "applied=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| EDIT_COUNT="$(jq -r '(.edits // []) | length' /tmp/workflow-remediation-result.json)" | |
| if [ "$EDIT_COUNT" = "0" ]; then | |
| echo "REMEDIATION_STATUS=no-patches" >> "$GITHUB_ENV" | |
| echo "applied=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| for file in $(jq -r '.edits[].file' /tmp/workflow-remediation-result.json); do | |
| case "$file" in | |
| *.yml|*.yaml) | |
| ruby -e 'require "yaml"; YAML.load_file(ARGV[0])' "$file" | |
| ;; | |
| *.json) | |
| jq empty "$file" | |
| ;; | |
| *.sh) | |
| bash -n "$file" | |
| ;; | |
| *.js|*.mjs|*.cjs) | |
| node --check "$file" | |
| ;; | |
| esac | |
| done | |
| if git diff --quiet; then | |
| echo "REMEDIATION_STATUS=no-changes" >> "$GITHUB_ENV" | |
| echo "applied=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git config user.name "ai-review[bot]" | |
| git config user.email "ai-review[bot]@users.noreply.github.com" | |
| git add .github scripts/ai-review-remediate.mjs | |
| git commit -m "[ai-fix] remediate workflow review findings" | |
| git push origin HEAD | |
| echo "REMEDIATION_STATUS=applied" >> "$GITHUB_ENV" | |
| echo "applied=true" >> "$GITHUB_OUTPUT" | |
| - name: Auto-remediate review findings | |
| if: | | |
| steps.changes.outputs.run_ai_review == 'true' && | |
| steps.version_bump.outputs.applied != 'true' && | |
| steps.workflow_remediate.outputs.applied != 'true' && | |
| env.AUTO_REMEDIATE_SCOPE != 'true' && | |
| (env.VERDICT == 'PASS_WITH_NOTES' || env.VERDICT == 'FAIL') && | |
| env.SKIP_REMEDIATION == 'false' && | |
| env.HAS_MINIMAX_KEY == 'true' | |
| env: | |
| MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| AFFECTED_FILES="$(jq -r '[.findings[].file // empty] | unique | .[]' /tmp/review-result.json)" | |
| FILE_COUNT="$(echo "$AFFECTED_FILES" | grep -c . || true)" | |
| if [ "$FILE_COUNT" -eq 0 ] || [ "$FILE_COUNT" -gt 5 ]; then | |
| echo "REMEDIATION_STATUS=skipped-scope" >> "$GITHUB_ENV" | |
| exit 0 | |
| fi | |
| FILE_CONTENTS="{}" | |
| for f in $AFFECTED_FILES; do | |
| if [ -f "$f" ]; then | |
| CONTENT="$(head -c 50000 "$f")" | |
| FILE_CONTENTS="$(echo "$FILE_CONTENTS" | jq --arg path "$f" --arg content "$CONTENT" '. + {($path): $content}')" | |
| fi | |
| done | |
| printf '%s' "$FILE_CONTENTS" > /tmp/remediation-file-contents.json | |
| jq -c '.findings' /tmp/review-result.json > /tmp/remediation-findings.json | |
| REMEDIATION_FINDINGS_FILE=/tmp/remediation-findings.json \ | |
| REMEDIATION_FILE_CONTENTS_FILE=/tmp/remediation-file-contents.json \ | |
| REMEDIATION_DIFF_FILE=/tmp/pr-diff.txt \ | |
| node scripts/ai-remediate.mjs > /tmp/remediation-result.json || true | |
| if [ ! -s /tmp/remediation-result.json ]; then | |
| echo "REMEDIATION_STATUS=error" >> "$GITHUB_ENV" | |
| exit 0 | |
| fi | |
| PATCH_COUNT="$(jq '.patches | length' /tmp/remediation-result.json)" | |
| if [ "$PATCH_COUNT" -eq 0 ]; then | |
| echo "REMEDIATION_STATUS=no-patches" >> "$GITHUB_ENV" | |
| exit 0 | |
| fi | |
| for i in $(seq 0 $((PATCH_COUNT - 1))); do | |
| PATCH_FILE="$(jq -r ".patches[$i].file" /tmp/remediation-result.json)" | |
| PATCH_CONTENT="$(jq -r ".patches[$i].content" /tmp/remediation-result.json)" | |
| if [ -f "$PATCH_FILE" ]; then | |
| printf '%s' "$PATCH_CONTENT" > "$PATCH_FILE" | |
| fi | |
| done | |
| for i in $(seq 0 $((PATCH_COUNT - 1))); do | |
| PATCH_FILE="$(jq -r ".patches[$i].file" /tmp/remediation-result.json)" | |
| case "$PATCH_FILE" in | |
| *.py) | |
| python -m py_compile "$PATCH_FILE" | |
| ;; | |
| *.json) | |
| jq empty "$PATCH_FILE" | |
| ;; | |
| esac | |
| done | |
| if git diff --quiet; then | |
| echo "REMEDIATION_STATUS=no-changes" >> "$GITHUB_ENV" | |
| exit 0 | |
| fi | |
| git config user.name "ai-review[bot]" | |
| git config user.email "ai-review[bot]@users.noreply.github.com" | |
| git add -A | |
| EXPLANATION="$(jq -r '.explanation // "Automated fixes"' /tmp/remediation-result.json | head -c 100)" | |
| git commit -m "[ai-fix] $EXPLANATION" | |
| git push origin HEAD | |
| echo "REMEDIATION_STATUS=applied" >> "$GITHUB_ENV" | |
| - name: Note auto-remediation push | |
| if: steps.workflow_remediate.outputs.applied == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| let summary = 'Applied workflow/config remediation to the PR branch.'; | |
| try { | |
| const result = JSON.parse(fs.readFileSync('/tmp/workflow-remediation-result.json', 'utf8')); | |
| if (typeof result.summary === 'string' && result.summary.trim()) { | |
| summary = result.summary.trim(); | |
| } | |
| } catch {} | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: [ | |
| '## AI Review Auto-Remediation', | |
| '', | |
| summary, | |
| '', | |
| 'A follow-up workflow run has been triggered on the updated branch.', | |
| ].join('\n'), | |
| }); | |
| - name: Publish review status | |
| if: always() && steps.changes.outputs.run_ai_review == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| REMEDIATION_STATUS="${REMEDIATION_STATUS:-none}" | |
| VERSION_BUMP_STATUS="${VERSION_BUMP_STATUS:-none}" | |
| VERDICT="${VERDICT:-FAIL}" | |
| SUMMARY="${SUMMARY:-Review failed}" | |
| if [ "$VERSION_BUMP_STATUS" = "applied" ]; then | |
| STATE="success" | |
| SUMMARY="AI version bump applied; rerun queued" | |
| elif [ "$REMEDIATION_STATUS" = "applied" ]; then | |
| STATE="success" | |
| SUMMARY="AI remediation applied; rerun queued" | |
| else | |
| case "$VERDICT" in | |
| PASS|PASS_WITH_NOTES|HIGH_RISK) | |
| STATE="success" | |
| ;; | |
| *) | |
| STATE="failure" | |
| ;; | |
| esac | |
| fi | |
| gh api repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }} \ | |
| -X POST \ | |
| -f state="$STATE" \ | |
| -f context="ai-review/verdict" \ | |
| -f description="$SUMMARY" \ | |
| -f target_url="${{ github.event.pull_request.html_url }}" | |
| - name: Comment on PR (if findings) | |
| if: steps.changes.outputs.run_ai_review == 'true' && env.REMEDIATION_STATUS != 'applied' && env.VERSION_BUMP_STATUS != 'applied' && env.VERDICT != 'PASS' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const result = JSON.parse(fs.readFileSync('/tmp/review-result.json', 'utf8')); | |
| const verdict = result.verdict || 'ERROR'; | |
| const summary = result.summary || ''; | |
| const findings = result.findings || []; | |
| const icon = { PASS: '\u2705', PASS_WITH_NOTES: '\u2705', FAIL: '\u274C', HIGH_RISK: '\u26A0\uFE0F' }[verdict] || '\u2753'; | |
| let body = `## ${icon} AI Review: ${verdict}\n\n${summary}\n\n`; | |
| if (findings.length > 0) { | |
| const grouped = { critical: [], warning: [], note: [] }; | |
| for (const f of findings) { | |
| const sev = grouped[f.severity] ? f.severity : 'note'; | |
| grouped[sev].push(f); | |
| } | |
| for (const [severity, items] of Object.entries(grouped)) { | |
| if (items.length === 0) continue; | |
| const label = { critical: 'Critical', warning: 'Warnings', note: 'Notes' }[severity]; | |
| body += `### ${label}\n\n`; | |
| for (const f of items) { | |
| const loc = f.file ? (f.line ? `\`${f.file}:${f.line}\`` : `\`${f.file}\``) : ''; | |
| body += `- ${loc ? `${loc} - ` : ''}${f.message}\n`; | |
| } | |
| body += '\n'; | |
| } | |
| } | |
| body += '---\n*Automated review by ViraCue AI Review*'; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const marker = '*Automated review by ViraCue AI Review*'; | |
| for (const comment of comments) { | |
| if (comment.body && comment.body.includes(marker)) { | |
| await github.rest.issues.deleteComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: comment.id, | |
| }); | |
| } | |
| } | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| - name: Enable auto-merge | |
| if: | | |
| steps.changes.outputs.run_ai_review == 'true' && | |
| env.REMEDIATION_STATUS != 'applied' && | |
| env.VERSION_BUMP_STATUS != 'applied' && | |
| ( | |
| env.VERDICT == 'PASS' || | |
| env.VERDICT == 'PASS_WITH_NOTES' || | |
| (env.VERDICT == 'HIGH_RISK' && env.AUTO_REMEDIATE_SCOPE == 'true' && env.FINDINGS_COUNT == '0' && env.HAS_CRITICAL != 'true') | |
| ) | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh pr merge ${{ github.event.pull_request.number }} --squash --auto 2>&1 || echo "Auto-merge enable failed (may already be enabled or branch protection requires review)" | |
| - name: Post human-approval-required (HIGH_RISK) | |
| if: | | |
| steps.changes.outputs.run_ai_review == 'true' && | |
| env.REMEDIATION_STATUS != 'applied' && | |
| env.VERDICT == 'HIGH_RISK' && | |
| env.AUTO_REMEDIATE_SCOPE != 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.payload.pull_request.number, | |
| body: [ | |
| '## Human Approval Required', | |
| '', | |
| 'The AI Code Reviewer classified this PR as **high risk**.', | |
| '', | |
| `Verdict: \`${process.env.VERDICT} - ${process.env.SUMMARY}\``, | |
| '', | |
| 'This PR will not auto-merge. A human must review and approve before merge.', | |
| ].join('\n'), | |
| }); | |
| - name: Publish skipped status for docs-only changes | |
| if: steps.changes.outputs.run_ai_review != 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh api repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }} \ | |
| -X POST \ | |
| -f state=success \ | |
| -f context="ai-review/verdict" \ | |
| -f description="PASS: AI review skipped for docs-only changes" \ | |
| -f target_url="${{ github.event.pull_request.html_url }}" |