diff --git a/.github/workflows/octo-pr-result-notify.yml b/.github/workflows/octo-pr-result-notify.yml index bbad5b8..5e6eda5 100644 --- a/.github/workflows/octo-pr-result-notify.yml +++ b/.github/workflows/octo-pr-result-notify.yml @@ -82,7 +82,7 @@ jobs: import urllib.error ALLOWED_KINDS = { - "pr_merged", "pr_closed", "pr_reopened", + "pr_merged", "pr_closed", "review_approved", "review_changes_requested" } @@ -170,6 +170,7 @@ jobs: pr_author = sanitize_text(require_env('PR_AUTHOR'), max_len=80) event_kind = require_env("EVENT_KIND") reviewer = sanitize_text(get_env('REVIEWER'), max_len=80) + reviewer_display = reviewer if reviewer else "(unknown)" pr_additions = int(get_env("PR_ADDITIONS", "0") or "0") pr_deletions = int(get_env("PR_DELETIONS", "0") or "0") pr_changed_files = int(get_env("PR_CHANGED_FILES", "0") or "0") @@ -199,22 +200,16 @@ jobs: f"\U0001f464 {pr_author}\n" f"\U0001f517 {pr_url}" ) - elif event_kind == "pr_reopened": - message = ( - f"\U0001f504 [{repo_name}] PR #{pr_number} reopened · {pr_title}\n" - f"\U0001f464 {pr_author}\n" - f"\U0001f517 {pr_url}" - ) elif event_kind == "review_approved": message = ( f"✅ [{repo_name}] PR #{pr_number} approved · {pr_title}\n" - f"\U0001f464 reviewer: {reviewer}\n" + f"\U0001f464 reviewer: {reviewer_display}\n" f"\U0001f517 {pr_url}" ) elif event_kind == "review_changes_requested": message = ( f"\U0001f501 [{repo_name}] PR #{pr_number} changes requested · {pr_title}\n" - f"\U0001f464 reviewer: {reviewer}\n" + f"\U0001f464 reviewer: {reviewer_display}\n" f"\U0001f517 {pr_url}" ) diff --git a/.github/workflows/octo-pr-review-feed.yml b/.github/workflows/octo-pr-review-feed.yml index 8a7d218..61ac0a4 100644 --- a/.github/workflows/octo-pr-review-feed.yml +++ b/.github/workflows/octo-pr-review-feed.yml @@ -60,7 +60,7 @@ jobs: import urllib.request import urllib.error - ALLOWED_ACTIONS = {"opened", "ready_for_review", "review_requested", "synchronize"} + ALLOWED_ACTIONS = {"ready_for_review", "review_requested"} def require_env(name): value = os.environ.get(name, "").strip() @@ -156,25 +156,16 @@ jobs: sys.exit(0) emoji_map = { - "opened": "\U0001f535", "ready_for_review": "✅", "review_requested": "\U0001f440", - "synchronize": "\U0001f501", } emoji = emoji_map[event_action] - if event_action == "synchronize": - message = ( - f"{emoji} [{repo_name}] PR #{pr_number} · {pr_title} — new commits pushed\n" - f"\U0001f464 {pr_author}\n" - f"\U0001f517 {pr_url}" - ) - else: - message = ( - f"{emoji} [{repo_name}] PR #{pr_number} · {pr_title}\n" - f"\U0001f464 {pr_author}\n" - f"\U0001f517 {pr_url}" - ) + message = ( + f"{emoji} [{repo_name}] PR #{pr_number} · {pr_title}\n" + f"\U0001f464 {pr_author}\n" + f"\U0001f517 {pr_url}" + ) failed = [] send_message(api_base_url, token, project_group_id, message, failed) diff --git a/.github/workflows/reusable-check-sprint.yml b/.github/workflows/reusable-check-sprint.yml index a36b2ad..35d7956 100644 --- a/.github/workflows/reusable-check-sprint.yml +++ b/.github/workflows/reusable-check-sprint.yml @@ -59,9 +59,13 @@ # # on: # pull_request_target: -# types: [opened, synchronize, reopened, edited] +# types: [opened, synchronize, reopened, edited, ready_for_review, converted_to_draft] # # Include "edited" so the check re-runs when the PR description is # # updated (e.g. developer adds "Closes #" after opening). +# # Include "ready_for_review" and "converted_to_draft" so the check +# # re-runs across draft↔ready transitions. REQUIRED for the draft-skip +# # to be safe: without "ready_for_review", a stale green from the draft +# # SHA persists after promotion to ready and bypasses the Sprint gate. # # jobs: # check-sprint: @@ -243,6 +247,48 @@ jobs: return datetime.now(ZoneInfo("Asia/Shanghai")).date() + # --- Step 0: Skip draft PRs before any board-state queries --------------- + # + # This must run BEFORE Step 1 (sprint resolution) so draft PRs are never + # blocked by board-health failures (missing Sprint field, no active + # iteration, insufficient token scopes, etc.). + # + # NOTE: Callers must include `ready_for_review` in their pull_request_target + # types to ensure this check re-evaluates when a draft PR is marked ready + # for review. Without it, the green "skipped" result cached on the draft + # SHA will remain valid after the PR is promoted to ready, allowing it to + # merge without Sprint validation. + # See: https://github.com/Mininglamp-OSS/.github/pull/51 + + draft_data = graphql( + """ + query($owner: String!, $name: String!, $number: Int!) { + repository(owner: $owner, name: $name) { + pullRequest(number: $number) { + isDraft + } + } + } + """, + variables={"owner": REPO_OWNER, "name": REPO_SHORT, "number": PR_NUMBER}, + ) + + try: + is_draft = draft_data["repository"]["pullRequest"]["isDraft"] + except (KeyError, TypeError) as e: + print(f"::error::Failed to read PR draft status for #{PR_NUMBER}. " + f"Detail: {e}") + sys.exit(1) + + if is_draft: + print("✅ Skipping Sprint check for draft PR.") + print("::notice::Draft PR detected. Sprint check skipped (exit 0).") + print("::notice::REQUIRED caller config: include `ready_for_review` " + "in `pull_request_target.types`. Without it, this green " + "result is cached on the draft SHA and persists after the " + "PR is marked ready, bypassing the Sprint gate.") + sys.exit(0) + # --- Step 1: Determine the current sprint iteration ----------------------- try: diff --git a/.github/workflows/reusable-pr-contributor-welcome.yml b/.github/workflows/reusable-pr-contributor-welcome.yml index f204473..bfbecc4 100644 --- a/.github/workflows/reusable-pr-contributor-welcome.yml +++ b/.github/workflows/reusable-pr-contributor-welcome.yml @@ -107,10 +107,15 @@ jobs: } const body = [ - `Hey @${username}, thanks for your first contribution to Octo! 🎉`, + `Hey @${username}, thanks for your first PR to Octo! 🎉`, '', - 'Welcome to the community, and looking forward to more! 🐙', + 'A maintainer will review it soon. While you wait, please make sure all CI checks pass.', '', + 'A few helpful links:', + '- 📖 [Contributing Guide](https://github.com/Mininglamp-OSS/.github/blob/main/CONTRIBUTING.md)', + '- 💬 [Discord Community](https://discord.gg/vj9Vsj9hSB)', + '', + 'Welcome aboard! 🐙', '— Octo Team', ].join('\n');