ci: consolidate workflows into ci.yml #2940
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: Claude Translation Review | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: "PR number to review" | |
| required: true | |
| type: number | |
| language: | |
| description: "Language code(s) to review (comma-separated, e.g. 'hi' or 'hi,bn')" | |
| required: false | |
| type: string | |
| full: | |
| description: "Re-review the entire PR (ignore prior-review SHA, force full diff)" | |
| required: false | |
| default: false | |
| type: boolean | |
| model: | |
| description: "Claude model for analysis" | |
| required: false | |
| default: "opus" | |
| type: choice | |
| options: | |
| - opus | |
| - sonnet | |
| - haiku | |
| fix: | |
| description: "Automatically fix critical translation issues" | |
| required: false | |
| default: false | |
| type: boolean | |
| issue_comment: | |
| types: [created] | |
| pull_request_review_comment: | |
| types: [created] | |
| pull_request: | |
| types: [opened] | |
| jobs: | |
| review-translations: | |
| # Runs when: | |
| # 1. Comment contains @claude /review-translations (from authorized user), OR | |
| # 2. PR is opened with title starting with "i18n:" (automatic), OR | |
| # 3. Manually dispatched from Actions tab with a PR number | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| ( | |
| github.event_name == 'issue_comment' && | |
| contains(github.event.comment.body, '@claude') && | |
| contains(github.event.comment.body, '/review-translations') && | |
| contains(fromJSON('["minimalsm","pettinarip","wackerow","nloureiro","konopkja","mnelsonBT","lukassim"]'), github.event.comment.user.login) && | |
| github.event.issue.pull_request | |
| ) || | |
| ( | |
| github.event_name == 'pull_request_review_comment' && | |
| contains(github.event.comment.body, '@claude') && | |
| contains(github.event.comment.body, '/review-translations') && | |
| contains(fromJSON('["minimalsm","pettinarip","wackerow","nloureiro","konopkja","mnelsonBT","lukassim"]'), github.event.comment.user.login) | |
| ) || | |
| ( | |
| github.event_name == 'pull_request' && | |
| startsWith(github.event.pull_request.title, 'i18n:') && | |
| startsWith(github.event.pull_request.head.ref, 'intl/pending-') && | |
| github.event.pull_request.head.repo.full_name == github.repository && | |
| contains(fromJSON('["minimalsm","pettinarip","wackerow","nloureiro","konopkja","mnelsonBT","lukassim"]'), github.event.pull_request.user.login) | |
| ) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: read | |
| id-token: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 1 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Get PR number | |
| id: pr | |
| env: | |
| # All values moved to env block to prevent shell injection | |
| EVENT_NAME: ${{ github.event_name }} | |
| INPUT_PR_NUMBER: ${{ github.event.inputs.pr_number }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| run: | | |
| if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then | |
| PR_NUM="$INPUT_PR_NUMBER" | |
| elif [[ "$EVENT_NAME" == "pull_request" || "$EVENT_NAME" == "pull_request_review_comment" ]]; then | |
| PR_NUM="$PR_NUMBER" | |
| else | |
| PR_NUM="$ISSUE_NUMBER" | |
| fi | |
| # Validate PR number is numeric to prevent injection in downstream usage | |
| if [[ ! "$PR_NUM" =~ ^[0-9]+$ ]]; then | |
| echo "Error: PR number must be numeric, got: $PR_NUM" | |
| exit 1 | |
| fi | |
| echo "number=$PR_NUM" >> $GITHUB_OUTPUT | |
| - name: Extract flags from comment | |
| id: parse | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| INPUT_LANGUAGE: ${{ github.event.inputs.language }} | |
| INPUT_FULL: ${{ github.event.inputs.full }} | |
| INPUT_MODEL: ${{ github.event.inputs.model }} | |
| INPUT_FIX: ${{ github.event.inputs.fix }} | |
| COMMENT_BODY: ${{ github.event.comment.body }} | |
| run: | | |
| # For automatic triggers (pull_request), use defaults (incremental scope) | |
| if [[ "$EVENT_NAME" == "pull_request" ]]; then | |
| echo "language_flag=" >> $GITHUB_OUTPUT | |
| echo "full_flag=" >> $GITHUB_OUTPUT | |
| echo "model=opus" >> $GITHUB_OUTPUT | |
| echo "fix_flag=" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # For manual dispatch, read directly from inputs | |
| if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then | |
| if [[ -n "$INPUT_LANGUAGE" && "$INPUT_LANGUAGE" =~ ^[a-zA-Z,-]+$ ]]; then | |
| echo "language_flag=--language=${INPUT_LANGUAGE}" >> $GITHUB_OUTPUT | |
| else | |
| echo "language_flag=" >> $GITHUB_OUTPUT | |
| fi | |
| # INPUT_MODEL is a choice type, safe values enforced by GitHub | |
| echo "model=${INPUT_MODEL}" >> $GITHUB_OUTPUT | |
| if [[ "$INPUT_FULL" == "true" ]]; then | |
| echo "full_flag=--full" >> $GITHUB_OUTPUT | |
| else | |
| echo "full_flag=" >> $GITHUB_OUTPUT | |
| fi | |
| if [[ "$INPUT_FIX" == "true" ]]; then | |
| echo "fix_flag=--fix" >> $GITHUB_OUTPUT | |
| else | |
| echo "fix_flag=" >> $GITHUB_OUTPUT | |
| fi | |
| exit 0 | |
| fi | |
| # COMMENT_BODY is passed via env to prevent shell injection. | |
| # Extract --language flag if present | |
| if [[ "$COMMENT_BODY" =~ --language=([a-zA-Z,-]+) ]]; then | |
| echo "language_flag=--language=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT | |
| else | |
| echo "language_flag=" >> $GITHUB_OUTPUT | |
| fi | |
| # Extract --full flag if present (override incremental, re-review entire PR) | |
| if [[ "$COMMENT_BODY" =~ --full([[:space:]]|$) ]]; then | |
| echo "full_flag=--full" >> $GITHUB_OUTPUT | |
| else | |
| echo "full_flag=" >> $GITHUB_OUTPUT | |
| fi | |
| # Extract --model flag if present (default to opus per skill spec) | |
| if [[ "$COMMENT_BODY" =~ --model=(opus|sonnet|haiku) ]]; then | |
| echo "model=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT | |
| else | |
| echo "model=opus" >> $GITHUB_OUTPUT | |
| fi | |
| # Extract --fix flag if present | |
| if [[ "$COMMENT_BODY" =~ --fix ]]; then | |
| echo "fix_flag=--fix" >> $GITHUB_OUTPUT | |
| else | |
| echo "fix_flag=" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Post acknowledgment | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| gh pr comment "$PR_NUMBER" --body "$(cat <<EOF | |
| :globe_with_meridians: **Translation review started.** [View progress]($RUN_URL) | |
| EOF | |
| )" | |
| - name: Run Claude Translation Review | |
| uses: anthropics/claude-code-action@v1 | |
| timeout-minutes: 120 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| claude_args: | | |
| --model ${{ steps.parse.outputs.model }} | |
| --allowedTools "Task,Glob,Grep,LS,Read,Edit,WebFetch,Bash(git status:*),Bash(git diff:*),Bash(git log:*),Bash(git fetch:*),Bash(git worktree:*),Bash(git add:*),Bash(git commit:*),Bash(gh api:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh pr comment:*),Bash(gh pr review:*)" | |
| prompt: | | |
| Execute the /review-translations command for PR #${{ steps.pr.outputs.number }}. | |
| Arguments: --pr=${{ steps.pr.outputs.number }} ${{ steps.parse.outputs.language_flag }} ${{ steps.parse.outputs.full_flag }} ${{ steps.parse.outputs.fix_flag }} | |
| Follow the instructions in .claude/commands/review-translations.md for Phase 0 (scope detection -- read prior review SHA from gh api .../reviews unless --full was passed), Phase 1 (parallel agents), and Phase 2 (collecting results). | |
| IMPORTANT workflow modifications for GitHub Actions context: | |
| 1. Use parallel Task agents (ONE agent per language) as specified in the skill. | |
| 2. SUBMIT THE REVIEW AS A PROPER PR REVIEW, NOT AN ISSUE COMMENT. | |
| This is mandatory: the next /review-translations invocation reads each PR Review's GitHub-attached `commit_id` to determine incremental scope. An issue comment (`gh pr comment`) does not carry a `commit_id` and would break the incremental flow. | |
| Write the body to a temp file (to avoid heredoc issues), then submit via `gh pr review`, which auto-attaches the current PR HEAD SHA: | |
| gh pr review ${{ steps.pr.outputs.number }} --comment --body-file /tmp/pr-review-body.md | |
| Default to `--comment`. Use `--approve` instead only when the review turned up ZERO critical issues (none found, or all auto-fixed in this same run). Never use `--request-changes`. | |
| 3. Body length: aim for a SINGLE review body. If it exceeds GitHub's body limit (~65k chars), keep the per-language summary table + scoring inside the Review body, and post ONLY the verbose per-language detail dumps as follow-up issue comments via `gh pr comment` (NOT additional Reviews -- supplemental detail does not need its own SHA marker). | |
| 4. The review body MUST include a `**Fixes:**` line near the top, with one of: | |
| - `Critical fixes applied: {N}` -- if --fix was passed AND fixes were committed (NOTE: this GitHub Actions context CANNOT auto-fix on the PR branch because we are not running in the PR branch's worktree; for now this case will not occur from CI) | |
| - `No fixes applied (review-only)` -- always the value when run from this workflow | |
| - `No critical issues found` -- when there are no critical issues at all | |
| This standardizes the format with the local /review-translations command. | |
| 5. Auto-fix behavior - check if --fix flag is present in Arguments above: | |
| - If --fix flag IS present: this CI workflow is not currently set up to commit fixes back to the PR branch. Note this in the review body and recommend running locally: `/review-translations --pr=${{ steps.pr.outputs.number }} --fix` | |
| - If --fix flag is NOT present: Do NOT apply fixes. At the end of your review body, if there are critical issues, include: | |
| --- | |
| **To apply fixes**, run locally: | |
| ``` | |
| /review-translations --pr=${{ steps.pr.outputs.number }} --fix | |
| ``` | |
| 6. Do NOT prompt for user input with AskUserQuestion - this is fully automated. |