name: Retarget main PRs to staging

# Mechanical enforcement of SHARED_RULES rule 8 ("Staging-first workflow, no
# exceptions"). When a bot opens a PR against main, retarget it to staging
# automatically and leave an explanatory comment. Human CEO-authored PRs (the
# staging→main promotion PR, etc.) are left alone — they're the authorised
# exception to the rule.
#
# Why an Action instead of only a prompt rule: prompt rules depend on every
# role's system-prompt.md staying in sync. Today 5 of 8 engineer roles
# (core-be, core-fe, app-fe, app-qa, devops-engineer) don't have the
# staging-first section — the bot keeps opening PRs to main. An Action
# enforces the invariant regardless of prompt drift.

on:
  pull_request_target:
    types: [opened, reopened]
    branches: [main]

permissions:
  pull-requests: write

jobs:
  retarget:
    name: Retarget to staging
    runs-on: ubuntu-latest
    # Only fire for bot-authored PRs. Human CEO PRs (staging→main promotion)
    # are intentional and pass through.
    #
    # Head-ref guard: never retarget a PR whose head IS `staging` — those
    # are the auto-promote staging→main PRs (opened by molecule-ai[bot]
    # since #2586 switched to an App token, which now passes the bot
    # filter below). Retargeting head=staging onto base=staging fails
    # with HTTP 422 "no new commits between base 'staging' and head
    # 'staging'", which used to surface as a noisy red workflow run on
    # every auto-promote (caught 2026-05-03 on PR #2588).
    if: >-
      github.event.pull_request.head.ref != 'staging'
      && (
        github.event.pull_request.user.type == 'Bot'
        || endsWith(github.event.pull_request.user.login, '[bot]')
        || github.event.pull_request.user.login == 'app/molecule-ai'
        || github.event.pull_request.user.login == 'molecule-ai[bot]'
      )
    steps:
      - name: Retarget PR base to staging
        id: retarget
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          PR_AUTHOR: ${{ github.event.pull_request.user.login }}
        # Issue #1884: when the bot opens a PR against main and there's
        # already another PR on the same head branch targeting staging,
        # GitHub's PATCH /pulls returns 422 with
        # "A pull request already exists for base branch 'staging' …".
        # The retarget can't proceed — but the right response is to
        # close the now-redundant main-PR, not to fail the workflow
        # noisily. Detect that specific 422 and close instead.
        run: |
          set +e
          echo "Retargeting PR #${PR_NUMBER} (author: ${PR_AUTHOR}) from main → staging"
          PATCH_OUTPUT=$(gh api -X PATCH \
            "repos/${{ github.repository }}/pulls/${PR_NUMBER}" \
            -f base=staging \
            --jq '.base.ref' 2>&1)
          PATCH_EXIT=$?
          set -e
          if [ "$PATCH_EXIT" -eq 0 ]; then
            echo "::notice::Retargeted PR #${PR_NUMBER} → staging"
            echo "outcome=retargeted" >> "$GITHUB_OUTPUT"
            exit 0
          fi
          # Specifically match the 422 duplicate-base/head error so
          # any OTHER PATCH failure (auth, deleted PR, etc.) still
          # surfaces as a real workflow failure.
          if echo "$PATCH_OUTPUT" | grep -q "pull request already exists for base branch 'staging'"; then
            echo "::notice::PR #${PR_NUMBER}: duplicate target-staging PR exists on same head — closing this main-PR as redundant."
            gh pr close "$PR_NUMBER" \
              --repo "${{ github.repository }}" \
              --comment "[retarget-bot] Closing — another PR on the same head branch already targets \`staging\`. This PR is redundant. See issue #1884 for the rationale."
            echo "outcome=closed-as-duplicate" >> "$GITHUB_OUTPUT"
            exit 0
          fi
          echo "::error::Retarget PATCH failed and was NOT a duplicate-base error:"
          echo "$PATCH_OUTPUT" >&2
          exit 1

      - name: Post explainer comment
        if: steps.retarget.outputs.outcome == 'retargeted'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
        run: |
          gh pr comment "$PR_NUMBER" \
            --repo "${{ github.repository }}" \
            --body "$(cat <<'BODY'
          [retarget-bot] This PR was opened against `main` and has been retargeted to `staging` automatically.

          **Why:** per [SHARED_RULES rule 8](https://git.moleculesai.app/molecule-ai/molecule-ai-org-template-molecule-dev/blob/main/SHARED_RULES.md), all feature work targets `staging` first; the CEO promotes `staging → main` separately.

          **What changed:** just the base branch — no code change. CI will re-run against `staging`. If you get merge conflicts, rebase on `staging`.

          **If this PR is the CEO's staging→main promotion:** the Action skipped you (only bot-authored PRs are retargeted). If you see this comment on your CEO PR, that's a bug — please tag @HongmingWang-Rabbit.
          BODY
          )"
