Skip to content

Bug Report: PyInstaller Build Missing Rich Unicode Data Module #1319

Bug Report: PyInstaller Build Missing Rich Unicode Data Module

Bug Report: PyInstaller Build Missing Rich Unicode Data Module #1319

# ============================================================================
# COMPLIANCE CHECK WORKFLOW
# ============================================================================
# Purpose: AI-powered compliance agent that verifies PRs are ready for merge
# by checking file group consistency, documentation updates, and
# enforcing project-specific merge requirements.
#
# Triggers:
# - AUTOMATICALLY after PR Review completes (for events that trigger both)
# - PR labeled with 'ready-for-merge'
# - PR marked ready for review
# - Comment with '/mirrobot-check' or '/mirrobot_check'
# - Manual workflow dispatch
#
# Workflow Dependency:
# - When triggered by ready_for_review, waits for PR Review to complete
# - When triggered independently (labels, comments), runs immediately
# - Ensures sequential execution only when both workflows trigger together
#
# Security Model:
# - Uses pull_request_target to run from base branch (trusted code)
# - Saves prompt from base branch BEFORE checking out PR code
# - Prevents prompt injection attacks from malicious PRs
#
# AI Behavior:
# - Multiple-turn analysis (one file/issue per turn)
# - Detailed issue descriptions for future self-analysis
# - Posts findings as PR comment and updates status checks
# ============================================================================
name: Compliance Check
# Prevent concurrent runs for the same PR
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number || github.event.workflow_run.pull_requests[0].number }}
cancel-in-progress: false
on:
# AUTOMATIC: Run after PR Review workflow completes
# This handles cases where both workflows would trigger together
# (e.g., ready_for_review, opened, synchronize)
workflow_run:
workflows: ["PR Review"]
types: [completed]
# SECURITY: Use pull_request_target (not pull_request) to run workflow from base branch
# This prevents malicious PRs from modifying the workflow or prompt files
# Note: ready_for_review removed - handled by workflow_run to ensure sequential execution
pull_request_target:
types: [labeled]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to check'
required: true
type: string
jobs:
compliance-check:
# Bot check is in the issue_comment branch - workflow shows "skipped" for bot comments
# Note: workflow_run is NOT in this condition - the trigger exists but job skips unless other conditions match
if: |
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request_target' &&
(github.event.action == 'ready_for_review' ||
(github.event.action == 'labeled' && contains(github.event.label.name, 'ready-for-merge')))) ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
github.event.comment.user.login != 'mirrobot' &&
github.event.comment.user.login != 'mirrobot-agent' &&
github.event.comment.user.login != 'mirrobot-agent[bot]' &&
(contains(github.event.comment.body, '/mirrobot-check') ||
contains(github.event.comment.body, '/mirrobot_check'))
)
runs-on: ubuntu-latest
# Minimal permissions following principle of least privilege
permissions:
contents: read # Read repository files
pull-requests: write # Post comments and reviews
statuses: write # Update commit status checks
issues: write # Post issue comments
env:
# -----------------------------------------------------------------------
# BASIC CONFIGURATION
# -----------------------------------------------------------------------
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number || github.event.workflow_run.pull_requests[0].number }}
BOT_NAMES_JSON: '["mirrobot", "mirrobot-agent", "mirrobot-agent[bot]"]'
# -----------------------------------------------------------------------
# FEATURE TOGGLES
# -----------------------------------------------------------------------
# ENABLE_REVIEWER_MENTIONS: Prepend @mentions to compliance report
# Set to 'true' to notify reviewers, 'false' to disable
ENABLE_REVIEWER_MENTIONS: 'false'
# -----------------------------------------------------------------------
# FILE GROUPS CONFIGURATION
# -----------------------------------------------------------------------
# Define file groups that the AI should check for consistency.
# Each group has:
# - name: Display name for the group
# - description: What to verify when files in this group change
# - files: List of file patterns (supports globs like docs/**/*.md)
#
# To add a new group, append to the JSON array below.
# The AI will check if changes to one file in a group require updates
# to other files in the same group (e.g., code + tests, manifest + lockfile)
FILE_GROUPS_JSON: |
[
{
"name": "GitHub Workflows",
"description": "When code changes affect the build or CI process, verify build.yml is updated with new steps, jobs, or release configurations. Check that code changes are reflected in build matrix, deploy steps, and CI/CD pipeline.",
"files": [
".github/workflows/build.yml",
".github/workflows/cleanup.yml"
]
},
{
"name": "Documentation",
"description": "Ensure README.md and DOCUMENTATION.md reflect code changes. For new features (providers, configuration options, CLI changes), verify feature documentation exists in both files. For API endpoint changes, check that DOCUMENTATION.md is updated. The 'Deployment guide.md' should be updated for deployment-related changes.",
"files": [
"README.md",
"DOCUMENTATION.md",
"Deployment guide.md",
"src/rotator_library/README.md"
]
},
{
"name": "Python Dependencies",
"description": "When requirements.txt changes, ensure all new dependencies are properly listed. When pyproject.toml in src/rotator_library changes, verify it's consistent with requirements.txt. No lockfile is required for this project, but verify dependency versions are compatible.",
"files": [
"requirements.txt",
"src/rotator_library/pyproject.toml"
]
},
{
"name": "Provider Configuration",
"description": "When adding or modifying LLM providers in src/rotator_library/providers/, ensure the provider is documented in DOCUMENTATION.md and README.md. New providers should have corresponding model definitions in model_definitions.py if needed.",
"files": [
"src/rotator_library/providers/**/*.py",
"src/rotator_library/model_definitions.py",
"src/rotator_library/provider_factory.py"
]
},
{
"name": "Proxy Application",
"description": "Changes to proxy_app endpoints, TUI launcher, or settings should be reflected in documentation. New CLI arguments should be documented in README.md Quick Start section.",
"files": [
"src/proxy_app/main.py",
"src/proxy_app/launcher_tui.py",
"src/proxy_app/settings_tool.py",
"src/proxy_app/batch_manager.py",
"src/proxy_app/detailed_logger.py"
]
}
]
steps:
# ========================================================================
# COMMENT VALIDATION STEP (only for issue_comment events)
# ========================================================================
# Validates that trigger words are in actual content (not in quotes/code)
# If validation fails, subsequent steps are skipped
# ========================================================================
- name: Validate comment trigger
id: validate
if: github.event_name == 'issue_comment'
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
set -e
# Save comment to temp file for processing
TEMP_FILE=$(mktemp)
echo "$COMMENT_BODY" > "$TEMP_FILE"
# Remove fenced code blocks (```...```)
CLEAN_BODY=$(awk '
/^```/ { in_code = !in_code; next }
!in_code { print }
' "$TEMP_FILE")
# Remove inline code (`...`)
CLEAN_BODY=$(echo "$CLEAN_BODY" | sed 's/`[^`]*`//g')
# Remove quoted lines (lines starting with >)
CLEAN_BODY=$(echo "$CLEAN_BODY" | grep -v '^[[:space:]]*>' || true)
rm -f "$TEMP_FILE"
echo "Clean body after stripping quotes/code:"
echo "$CLEAN_BODY"
echo "---"
# Check for trigger words in clean text
# Trigger: /mirrobot-check or /mirrobot_check
if echo "$CLEAN_BODY" | grep -qE '/mirrobot[-_]check'; then
echo "::notice::Valid trigger found in non-quoted, non-code text."
echo "should_proceed=true" >> $GITHUB_OUTPUT
else
echo "::notice::Trigger only found in quotes/code blocks. Skipping."
echo "should_proceed=false" >> $GITHUB_OUTPUT
fi
# ======================================================================
# PHASE 1: SECURE SETUP
# ======================================================================
# SECURITY: Checkout base branch first to access trusted prompt file.
# This prevents malicious PRs from injecting code into the AI prompt.
- name: Checkout base branch (for trusted prompt)
if: github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true'
uses: actions/checkout@v4
# Initialize bot credentials and OpenCode API access
- name: Bot Setup
if: github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true'
id: setup
uses: ./.github/actions/bot-setup
with:
bot-app-id: ${{ secrets.BOT_APP_ID }}
bot-private-key: ${{ secrets.BOT_PRIVATE_KEY }}
opencode-api-key: ${{ secrets.OPENCODE_API_KEY }}
opencode-model: ${{ secrets.OPENCODE_MODEL }}
opencode-fast-model: ${{ secrets.OPENCODE_FAST_MODEL }}
custom-providers-json: ${{ secrets.CUSTOM_PROVIDERS_JSON }}
# ======================================================================
# CONDITIONAL WAIT: Wait for PR Review to Complete
# ======================================================================
# Only wait when triggered by ready_for_review event
# This ensures sequential execution: PR Review → Compliance Check
# For other triggers (labels, comments), skip and proceed immediately
- name: Wait for PR Review Workflow (if triggered by ready_for_review)
if: (github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true') && github.event.action == 'ready_for_review'
env:
GH_TOKEN: ${{ steps.setup.outputs.token }}
run: |
echo "Triggered by ready_for_review - waiting for PR Review to complete..."
# Wait up to 30 minutes (180 checks * 10 seconds)
MAX_ATTEMPTS=180
ATTEMPT=0
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
# Get latest PR Review workflow run for this PR
REVIEW_STATUS=$(gh run list \
--repo ${{ github.repository }} \
--workflow "PR Review" \
--json status,conclusion,headSha \
--jq "[.[] | select(.headSha == \"${{ github.event.pull_request.head.sha }}\")][0] | {status, conclusion}")
STATUS=$(echo "$REVIEW_STATUS" | jq -r '.status // "not_found"')
CONCLUSION=$(echo "$REVIEW_STATUS" | jq -r '.conclusion // ""')
echo "Attempt $((ATTEMPT + 1))/$MAX_ATTEMPTS: PR Review status=$STATUS, conclusion=$CONCLUSION"
if [ "$STATUS" == "completed" ]; then
echo "✅ PR Review completed with conclusion: $CONCLUSION"
break
elif [ "$STATUS" == "not_found" ]; then
echo "⚠️ No PR Review workflow run found yet, waiting..."
else
echo "⏳ PR Review still running ($STATUS), waiting..."
fi
sleep 10
ATTEMPT=$((ATTEMPT + 1))
done
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
echo "::warning::Timed out waiting for PR Review workflow (waited 30 minutes)"
echo "Proceeding with compliance check anyway..."
fi
# ======================================================================
# PHASE 2: GATHER PR CONTEXT
# ======================================================================
# Fetch PR metadata: title, author, files changed, labels, reviewers
- name: Get PR Metadata
if: github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true'
id: pr_info
env:
GH_TOKEN: ${{ steps.setup.outputs.token }}
run: |
pr_json=$(gh pr view ${{ env.PR_NUMBER }} --repo ${{ github.repository }} --json author,title,body,headRefOid,files,labels,reviewRequests)
echo "head_sha=$(echo "$pr_json" | jq -r .headRefOid)" >> $GITHUB_OUTPUT
echo "pr_title=$(echo "$pr_json" | jq -r .title)" >> $GITHUB_OUTPUT
# Extract author to shell variable first (can't self-reference step outputs)
pr_author=$(echo "$pr_json" | jq -r .author.login)
echo "pr_author=$pr_author" >> $GITHUB_OUTPUT
pr_body=$(echo "$pr_json" | jq -r '.body // ""')
echo "pr_body<<EOF" >> $GITHUB_OUTPUT
echo "$pr_body" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Changed files as space-separated list
changed_files=$(echo "$pr_json" | jq -r '.files[] | .path' | tr '\n' ' ')
echo "changed_files=$changed_files" >> $GITHUB_OUTPUT
# Changed files as JSON array
files_json=$(echo "$pr_json" | jq -c '[.files[] | .path]')
echo "files_json=$files_json" >> $GITHUB_OUTPUT
# Labels as JSON array
labels_json=$(echo "$pr_json" | jq -c '[.labels[] | .name]')
echo "labels_json=$labels_json" >> $GITHUB_OUTPUT
# Requested reviewers for mentions
reviewers=$(echo "$pr_json" | jq -r '.reviewRequests[]? | .login' | tr '\n' ' ')
mentions="@$pr_author"
if [ -n "$reviewers" ]; then
for reviewer in $reviewers; do
mentions="$mentions @$reviewer"
done
fi
echo "reviewer_mentions=$reviewers" >> $GITHUB_OUTPUT
echo "all_mentions=$mentions" >> $GITHUB_OUTPUT
# Retrieve previous compliance check results for this PR
# This allows the AI to track previously identified issues
- name: Fetch Previous Compliance Reviews
if: github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true'
id: prev_reviews
env:
GH_TOKEN: ${{ steps.setup.outputs.token }}
BOT_NAMES_JSON: ${{ env.BOT_NAMES_JSON }}
run: |
# Find previous compliance review comments by this bot
reviews=$(gh api "/repos/${{ github.repository }}/issues/${{ env.PR_NUMBER }}/comments" \
--paginate | jq -r --argjson bots "$BOT_NAMES_JSON" '
map(select(
(.user.login as $u | $bots | index($u)) and
(.body | contains("<!-- compliance-check-id:"))
))
| map(
# Extract commit SHA from marker
(.body | capture("<!-- compliance-check-id: [0-9]+-(?<sha>[a-f0-9]+) -->") | .sha) as $commit_sha |
"## Previous Compliance Review\n" +
"**Date**: " + .created_at + "\n" +
"**Commit**: " + $commit_sha + "\n\n" +
.body
)
| join("\n\n---\n\n")
')
if [ -n "$reviews" ]; then
echo "PREVIOUS_REVIEWS<<EOF" >> $GITHUB_OUTPUT
echo "$reviews" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "PREVIOUS_REVIEWS=" >> $GITHUB_OUTPUT
fi
# ======================================================================
# PHASE 3: SECURITY CHECKPOINT
# ======================================================================
# CRITICAL: Save the trusted prompt from base branch to /tmp BEFORE
# checking out PR code. This prevents prompt injection attacks.
- name: Save secure prompt from base branch
if: github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true'
run: cp .github/prompts/compliance-check.md /tmp/compliance-check.md
# NOW it's safe to checkout the PR code (untrusted)
# The prompt is already secured in /tmp
- name: Checkout PR Head for Diff Generation
if: github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true'
uses: actions/checkout@v4
with:
ref: ${{ steps.pr_info.outputs.head_sha }}
fetch-depth: 0 # Full history needed for diff
# Generate a unified diff of all PR changes for the AI to analyze
# The diff is saved to a file for efficient context usage
- name: Generate PR Diff
if: github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true'
id: diff
run: |
mkdir -p "$GITHUB_WORKSPACE/.mirrobot_files"
# Get base branch from PR
pr_json=$(gh pr view ${{ env.PR_NUMBER }} --repo ${{ github.repository }} --json baseRefName)
BASE_BRANCH=$(echo "$pr_json" | jq -r .baseRefName)
CURRENT_SHA="${{ steps.pr_info.outputs.head_sha }}"
echo "Generating PR diff against base branch: $BASE_BRANCH"
# Fetch base branch
if git fetch origin "$BASE_BRANCH":refs/remotes/origin/"$BASE_BRANCH" 2>/dev/null; then
echo "Successfully fetched base branch $BASE_BRANCH"
# Find merge base
if MERGE_BASE=$(git merge-base origin/"$BASE_BRANCH" "$CURRENT_SHA" 2>/dev/null); then
echo "Found merge base: $MERGE_BASE"
# Generate diff
if DIFF_CONTENT=$(git diff --patch "$MERGE_BASE".."$CURRENT_SHA" 2>/dev/null); then
DIFF_SIZE=${#DIFF_CONTENT}
DIFF_LINES=$(echo "$DIFF_CONTENT" | wc -l)
echo "Generated PR diff: $DIFF_LINES lines, $DIFF_SIZE characters"
# Truncate if too large (500KB limit)
if [ $DIFF_SIZE -gt 500000 ]; then
echo "::warning::PR diff is very large ($DIFF_SIZE chars). Truncating to 500KB."
TRUNCATION_MSG=$'\n\n[DIFF TRUNCATED - PR is very large. Showing first 500KB only.]'
DIFF_CONTENT="${DIFF_CONTENT:0:500000}${TRUNCATION_MSG}"
fi
echo "$DIFF_CONTENT" > "$GITHUB_WORKSPACE/.mirrobot_files/pr_diff.txt"
echo "diff_path=$GITHUB_WORKSPACE/.mirrobot_files/pr_diff.txt" >> $GITHUB_OUTPUT
else
echo "::warning::Could not generate diff. Using changed files list only."
echo "(Diff generation failed. Please refer to the changed files list.)" > "$GITHUB_WORKSPACE/.mirrobot_files/pr_diff.txt"
echo "diff_path=$GITHUB_WORKSPACE/.mirrobot_files/pr_diff.txt" >> $GITHUB_OUTPUT
fi
else
echo "::warning::Could not find merge base."
echo "(No common ancestor found.)" > "$GITHUB_WORKSPACE/.mirrobot_files/pr_diff.txt"
echo "diff_path=$GITHUB_WORKSPACE/.mirrobot_files/pr_diff.txt" >> $GITHUB_OUTPUT
fi
else
echo "::warning::Could not fetch base branch."
echo "(Base branch not available for diff.)" > "$GITHUB_WORKSPACE/.mirrobot_files/pr_diff.txt"
echo "diff_path=$GITHUB_WORKSPACE/.mirrobot_files/pr_diff.txt" >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ steps.setup.outputs.token }}
# ======================================================================
# PHASE 4: PREPARE AI CONTEXT
# ======================================================================
# Convert FILE_GROUPS_JSON to human-readable format for AI prompt
- name: Format File Groups for Prompt
if: github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true'
id: file_groups
run: |
# Convert JSON config to human-readable format for the AI
echo "FILE GROUPS FOR COMPLIANCE CHECKING:" > /tmp/file_groups.txt
echo "" >> /tmp/file_groups.txt
# Parse JSON and format for prompt
echo "$FILE_GROUPS_JSON" | jq -r '.[] |
"Group: \(.name)\n" +
"Description: \(.description)\n" +
"Files:\n" +
(.files | map(" - \(.)") | join("\n")) +
"\n"
' >> /tmp/file_groups.txt
echo "FILE_GROUPS_PATH=/tmp/file_groups.txt" >> $GITHUB_OUTPUT
# Create template structure for the compliance report
# AI will fill in the analysis sections
- name: Generate Report Template
if: github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true'
id: template
run: |
cat > /tmp/report_template.md <<'TEMPLATE'
## 🔍 Compliance Check Results
### Status: [TO_BE_DETERMINED]
**PR**: #${{ env.PR_NUMBER }} - ${{ steps.pr_info.outputs.pr_title }}
**Author**: @${{ steps.pr_info.outputs.pr_author }}
**Commit**: ${{ steps.pr_info.outputs.head_sha }}
**Checked**: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
---
### 📊 Summary
[AI to complete: Brief overview of analysis]
---
### 📁 File Groups Analyzed
[AI to complete: Fill in analysis for each affected group]
---
### 🎯 Overall Assessment
[AI to complete: Holistic compliance state]
### 📝 Next Steps
[AI to complete: Actionable guidance]
---
_Compliance verification by AI agent • Re-run with `/mirrobot-check`_
<!-- compliance-check-id: ${{ env.PR_NUMBER }}-${{ steps.pr_info.outputs.head_sha }} -->
TEMPLATE
echo "TEMPLATE_PATH=/tmp/report_template.md" >> $GITHUB_OUTPUT
# ======================================================================
# PHASE 5: AI ANALYSIS
# ======================================================================
# Substitute environment variables into the prompt template
# Uses the TRUSTED prompt from /tmp (not from PR code)
- name: Assemble Compliance Prompt
if: github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true'
env:
PR_NUMBER: ${{ env.PR_NUMBER }}
PR_TITLE: ${{ steps.pr_info.outputs.pr_title }}
PR_BODY: ${{ steps.pr_info.outputs.pr_body }}
PR_AUTHOR: ${{ steps.pr_info.outputs.pr_author }}
PR_HEAD_SHA: ${{ steps.pr_info.outputs.head_sha }}
CHANGED_FILES: ${{ steps.pr_info.outputs.changed_files }}
CHANGED_FILES_JSON: ${{ steps.pr_info.outputs.files_json }}
PR_LABELS: ${{ steps.pr_info.outputs.labels_json }}
PREVIOUS_REVIEWS: ${{ steps.prev_reviews.outputs.PREVIOUS_REVIEWS }}
FILE_GROUPS: ${{ steps.file_groups.outputs.FILE_GROUPS_PATH }}
REPORT_TEMPLATE: ${{ steps.template.outputs.TEMPLATE_PATH }}
DIFF_PATH: ${{ steps.diff.outputs.diff_path }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
TMP_DIR="${RUNNER_TEMP:-/tmp}"
VARS='${PR_NUMBER} ${PR_TITLE} ${PR_BODY} ${PR_AUTHOR} ${PR_HEAD_SHA} ${CHANGED_FILES} ${CHANGED_FILES_JSON} ${PR_LABELS} ${PREVIOUS_REVIEWS} ${FILE_GROUPS} ${REPORT_TEMPLATE} ${DIFF_PATH} ${GITHUB_REPOSITORY}'
envsubst "$VARS" < /tmp/compliance-check.md > "$TMP_DIR/assembled_prompt.txt"
# Execute the AI compliance check
# The AI will analyze the PR using multiple turns (5-20+ expected)
# and post its findings as a comment + status check
- name: Run Compliance Check with OpenCode
if: github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true'
env:
GITHUB_TOKEN: ${{ steps.setup.outputs.token }}
OPENCODE_PERMISSION: |
{
"bash": {
"gh*": "allow",
"git*": "allow",
"jq*": "allow",
"cat*": "allow"
},
"external_directory": "allow",
"webfetch": "deny"
}
PR_NUMBER: ${{ env.PR_NUMBER }}
GITHUB_REPOSITORY: ${{ github.repository }}
PR_HEAD_SHA: ${{ steps.pr_info.outputs.head_sha }}
run: |
TMP_DIR="${RUNNER_TEMP:-/tmp}"
opencode run --share - < "$TMP_DIR/assembled_prompt.txt"
# ======================================================================
# PHASE 6: POST-PROCESSING (OPTIONAL)
# ======================================================================
# If enabled, prepend @reviewer mentions to the compliance report
# This is controlled by ENABLE_REVIEWER_MENTIONS at the top
- name: Prepend Reviewer Mentions to Posted Comment
if: always() && (github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true') && env.ENABLE_REVIEWER_MENTIONS == 'true'
continue-on-error: true
env:
GH_TOKEN: ${{ steps.setup.outputs.token }}
BOT_NAMES_JSON: ${{ env.BOT_NAMES_JSON }}
REVIEWER_MENTIONS: ${{ steps.pr_info.outputs.reviewer_mentions }}
PR_AUTHOR: ${{ steps.pr_info.outputs.pr_author }}
run: |
sleep 3 # Wait for comment to be posted
# Find the compliance comment just posted by the bot
latest_comment=$(gh api "/repos/${{ github.repository }}/issues/${{ env.PR_NUMBER }}/comments" \
--paginate | jq -r --argjson bots "$BOT_NAMES_JSON" '
map(select(.user.login as $u | $bots | index($u)))
| sort_by(.created_at)
| last
| {id: .id, body: .body}
')
comment_id=$(echo "$latest_comment" | jq -r .id)
current_body=$(echo "$latest_comment" | jq -r .body)
# Build reviewer mentions (excluding author since already in template)
reviewer_mentions=""
if [ -n "$REVIEWER_MENTIONS" ]; then
for reviewer in $REVIEWER_MENTIONS; do
if [ "$reviewer" != "$PR_AUTHOR" ]; then
reviewer_mentions="$reviewer_mentions @$reviewer"
fi
done
fi
# Prepend reviewer mentions if any exist
if [ -n "$reviewer_mentions" ]; then
new_body="$reviewer_mentions
$current_body"
gh api --method PATCH "/repos/${{ github.repository }}/issues/comments/$comment_id" \
-f body="$new_body"
echo "✓ Prepended reviewer mentions: $reviewer_mentions"
else
echo "No additional reviewers to mention"
fi
- name: Verify Compliance Review Footers
if: always() && (github.event_name != 'issue_comment' || steps.validate.outputs.should_proceed == 'true')
continue-on-error: true
env:
GH_TOKEN: ${{ steps.setup.outputs.token }}
BOT_NAMES_JSON: ${{ env.BOT_NAMES_JSON }}
PR_NUMBER: ${{ env.PR_NUMBER }}
PR_HEAD_SHA: ${{ steps.pr_info.outputs.head_sha }}
run: |
set -e
sleep 5 # Wait for API consistency
echo "Verifying latest compliance review for required footers..."
# Find latest bot comment with compliance marker
latest_comment=$(gh api "/repos/${{ github.repository }}/issues/${{ env.PR_NUMBER }}/comments" \
--paginate | jq -r --argjson bots "$BOT_NAMES_JSON" '
map(select(.user.login as $u | $bots | index($u)))
| sort_by(.created_at)
| last
| {id: .id, body: .body}
')
comment_id=$(echo "$latest_comment" | jq -r .id)
current_body=$(echo "$latest_comment" | jq -r .body)
EXPECTED_SIGNATURE="_Compliance verification by AI agent"
EXPECTED_MARKER="<!-- compliance-check-id: ${{ env.PR_NUMBER }}-${{ steps.pr_info.outputs.head_sha }} -->"
needs_fix=false
if [[ "$current_body" != *"$EXPECTED_SIGNATURE"* ]]; then
echo "::warning::Missing compliance signature footer."
needs_fix=true
fi
if [[ "$current_body" != *"compliance-check-id:"* ]]; then
echo "::warning::Missing compliance-check-id marker."
needs_fix=true
fi
if [ "$needs_fix" = true ]; then
echo "::error::Compliance review missing required footers."
exit 1
else
echo "✓ Verification passed!"
fi