mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
ci(autofix): add single-target scheduler
This commit is contained in:
parent
5d2bfbd21b
commit
295ffd9018
4 changed files with 201 additions and 38 deletions
120
.github/workflows/qwen-autofix.yml
vendored
120
.github/workflows/qwen-autofix.yml
vendored
|
|
@ -8,22 +8,24 @@ name: 'Qwen Autofix'
|
|||
# The lifecycle is asynchronous — a PR is opened in one run and its review is
|
||||
# addressed in a later run once a reviewer has weighed in — so each scheduled
|
||||
# tick runs only the phase(s) that make sense, decided by the `route` job:
|
||||
# • every 4h → review phase (sweep the bot's open PRs)
|
||||
# • every 12h (00/12 UTC) → also the issue phase (locate + fix one new bug)
|
||||
# • every 10m → review phase; issue phase only if no PR needs work
|
||||
# • issues:labeled → issue phase when ready label, state, and sender match
|
||||
# • pull_request_review → review phase for submitted feedback on bot PRs
|
||||
# • workflow_dispatch → force a phase, an issue, or a PR
|
||||
#
|
||||
# Every GitHub write (issue/PR comments, labels, branch push, PR create) goes
|
||||
# through CI_DEV_BOT_PAT so the bot acts as a single identity, qwen-code-dev-bot.
|
||||
# through CI_DEV_BOT_PAT so the bot acts as the configured autofix identity.
|
||||
# PAT label writes can emit issues:labeled events; the route guards below make
|
||||
# those runs exit unless the label, issue state, and ready label all match.
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- 'labeled'
|
||||
pull_request_review:
|
||||
types:
|
||||
- 'submitted'
|
||||
schedule:
|
||||
- cron: '0 0,12 * * *' # Issue + review every 12h; must match route SCHEDULE check
|
||||
- cron: '0 4,8,16,20 * * *' # Review only between issue runs
|
||||
- cron: '*/10 * * * *' # Review first; issue fallback only when no PR needs work
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
phase:
|
||||
|
|
@ -60,7 +62,7 @@ permissions:
|
|||
env:
|
||||
# Identity of the autofix bot and the branches it owns. Only its own in-repo
|
||||
# PRs are ever eligible for the review phase — never a fork or another branch.
|
||||
AUTOFIX_BOT: 'qwen-code-dev-bot'
|
||||
AUTOFIX_BOT: "${{ vars.AUTOFIX_BOT_LOGIN || 'qwen-code-dev-bot' }}"
|
||||
BRANCH_PREFIX: 'autofix/issue-'
|
||||
# The automated Qwen PR reviewer posts as this account; its review counts as
|
||||
# actionable feedback even though it is not a human collaborator.
|
||||
|
|
@ -71,7 +73,9 @@ env:
|
|||
TRUSTED_ASSOC: '["OWNER", "MEMBER", "COLLABORATOR"]'
|
||||
# Hard cap on automated review-address rounds per PR. After this the bot stops
|
||||
# and leaves the PR for a human.
|
||||
MAX_ROUNDS: '3'
|
||||
MAX_ROUNDS: '5'
|
||||
# Do not claim more issues when too many existing autofix PRs are still open.
|
||||
MAX_OPEN_AUTOFIX_PRS: '5'
|
||||
|
||||
jobs:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -110,6 +114,8 @@ jobs:
|
|||
REPO: '${{ github.repository }}'
|
||||
SENDER_LOGIN: '${{ github.event.sender.login }}'
|
||||
SCHEDULE: '${{ github.event.schedule }}'
|
||||
PR_AUTHOR: '${{ github.event.pull_request.user.login }}'
|
||||
PR_NUMBER_EVENT: '${{ github.event.pull_request.number }}'
|
||||
run: |-
|
||||
DO_ISSUE=false
|
||||
DO_REVIEW=false
|
||||
|
|
@ -137,10 +143,21 @@ jobs:
|
|||
if [[ "${EVENT_NAME}" == 'schedule' || "${EVENT_NAME}" == 'workflow_dispatch' ]]; then
|
||||
DO_REVIEW=true
|
||||
fi
|
||||
# Must match the issue-phase cron string on the schedule trigger.
|
||||
if [[ "${EVENT_NAME}" == 'schedule' && "${SCHEDULE}" == '0 0,12 * * *' ]]; then
|
||||
# Scheduled runs scan review PRs first; issue-autofix runs only
|
||||
# when review-scan reports no target.
|
||||
if [[ "${EVENT_NAME}" == 'schedule' ]]; then
|
||||
DO_ISSUE=true
|
||||
fi
|
||||
if [[ "${EVENT_NAME}" == 'pull_request_review' ]]; then
|
||||
DO_ISSUE=false
|
||||
if [[ "${PR_AUTHOR}" == "${AUTOFIX_BOT}" ]]; then
|
||||
DO_REVIEW=true
|
||||
ROUTE_PR="$(sanitize_number "${PR_NUMBER_EVENT}")"
|
||||
echo "🧭 review event on bot PR #${PR_NUMBER_EVENT} → review phase"
|
||||
else
|
||||
echo "🧭 review event ignored: PR author '${PR_AUTHOR:-n/a}' is not ${AUTOFIX_BOT}"
|
||||
fi
|
||||
fi
|
||||
if [[ "${EVENT_NAME}" == 'issues' ]]; then
|
||||
DO_REVIEW=false
|
||||
label_is_trigger=false
|
||||
|
|
@ -190,15 +207,19 @@ jobs:
|
|||
echo "dry_run=${DRY_RUN}" >> "${GITHUB_OUTPUT}"
|
||||
echo "issue_number=${ROUTE_ISSUE}" >> "${GITHUB_OUTPUT}"
|
||||
echo "pr_number=${ROUTE_PR}" >> "${GITHUB_OUTPUT}"
|
||||
echo "🧭 phase='${PHASE:-auto}' event='${EVENT_NAME}' issue='#${ISSUE_NUMBER:-n/a}' schedule='${SCHEDULE:-n/a}' dry_run=${DRY_RUN} → issue=${DO_ISSUE} review=${DO_REVIEW}"
|
||||
echo "🧭 phase='${PHASE:-auto}' event='${EVENT_NAME}' issue='#${ISSUE_NUMBER:-n/a}' pr='#${PR_NUMBER_EVENT:-n/a}' schedule='${SCHEDULE:-n/a}' dry_run=${DRY_RUN} → issue=${DO_ISSUE} review=${DO_REVIEW}"
|
||||
|
||||
# ===========================================================================
|
||||
# ISSUE PHASE — locate one maintainer-ready issue, fix it, open a PR.
|
||||
# ===========================================================================
|
||||
issue-autofix:
|
||||
needs: 'route'
|
||||
needs: ['route', 'review-scan']
|
||||
if: |-
|
||||
${{ needs.route.outputs.do_issue == 'true' }}
|
||||
${{
|
||||
always() &&
|
||||
needs.route.outputs.do_issue == 'true' &&
|
||||
(github.event_name != 'schedule' || (needs.review-scan.result == 'success' && needs.review-scan.outputs.has_targets != 'true'))
|
||||
}}
|
||||
runs-on: 'ubuntu-latest'
|
||||
timeout-minutes: 180
|
||||
concurrency:
|
||||
|
|
@ -319,6 +340,7 @@ jobs:
|
|||
FORCED_ISSUE: '${{ needs.route.outputs.issue_number || github.event.issue.number }}'
|
||||
run: |-
|
||||
mkdir -p "${WORKDIR}"
|
||||
OPEN_AUTOFIX_PR_COUNT=0
|
||||
|
||||
if [[ -n "${FORCED_ISSUE}" ]]; then
|
||||
echo "🎯 Forced issue #${FORCED_ISSUE}"
|
||||
|
|
@ -354,34 +376,51 @@ jobs:
|
|||
fi
|
||||
fi
|
||||
else
|
||||
echo "🔍 Ready-for-agent issues (newest first)..."
|
||||
if ! gh issue list --repo "${REPO}" \
|
||||
if ! gh pr list --repo "${REPO}" --state open --author "${AUTOFIX_BOT}" \
|
||||
--limit 100 --json number,headRefName,isCrossRepository > "${WORKDIR}/open-autofix-prs.json"; then
|
||||
echo "::warning::Open autofix PR scan failed; proceeding without WIP-cap enforcement."
|
||||
else
|
||||
OPEN_AUTOFIX_PR_COUNT="$(jq --arg p "${BRANCH_PREFIX}" \
|
||||
'[.[] | select((.isCrossRepository != true) and ((.headRefName // "") | startswith($p)))] | length' \
|
||||
"${WORKDIR}/open-autofix-prs.json")"
|
||||
fi
|
||||
|
||||
if [[ "${OPEN_AUTOFIX_PR_COUNT}" -ge "${MAX_OPEN_AUTOFIX_PRS}" ]]; then
|
||||
echo "⏭️ ${OPEN_AUTOFIX_PR_COUNT} open autofix PR(s) already exist; WIP limit is ${MAX_OPEN_AUTOFIX_PRS}; skipping issue fallback."
|
||||
jq -n -c '[]' > "${WORKDIR}/candidates.json"
|
||||
else
|
||||
echo "🔍 Ready-for-agent issues (newest first)..."
|
||||
if ! gh issue list --repo "${REPO}" \
|
||||
--search "is:open is:issue label:${READY_FOR_AGENT_LABEL} label:${AUTOFIX_APPROVED_LABEL} ${AUTOFIX_ISSUE_EXCLUDES}" \
|
||||
--limit 30 --json number,title,body,labels,createdAt,url \
|
||||
> "${WORKDIR}/scan.json"; then
|
||||
echo "::warning::Ready-for-agent issue scan failed; falling back to an empty candidate list."
|
||||
jq -n -c '[]' > "${WORKDIR}/candidates.json"
|
||||
else
|
||||
if ! jq -c '.[0:10] | map(. + {autofixTier: 1})' \
|
||||
"${WORKDIR}/scan.json" > "${WORKDIR}/candidates.json"; then
|
||||
echo "::warning::Ready-for-agent result processing failed; falling back to an empty candidate list."
|
||||
echo "::warning::Ready-for-agent issue scan failed; falling back to an empty candidate list."
|
||||
jq -n -c '[]' > "${WORKDIR}/candidates.json"
|
||||
else
|
||||
if ! jq -c '.[0:10] | map(. + {autofixTier: 1})' \
|
||||
"${WORKDIR}/scan.json" > "${WORKDIR}/candidates.json"; then
|
||||
echo "::warning::Ready-for-agent result processing failed; falling back to an empty candidate list."
|
||||
jq -n -c '[]' > "${WORKDIR}/candidates.json"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
COUNT="$(jq length "${WORKDIR}/candidates.json")"
|
||||
if [[ "${COUNT}" -gt 0 ]]; then
|
||||
if ! gh pr list --repo "${REPO}" --state open --author "${AUTOFIX_BOT}" \
|
||||
--limit 100 --json number,headRefName > "${WORKDIR}/open-autofix-prs.json"; then
|
||||
if [[ -s "${WORKDIR}/open-autofix-prs.json" ]]; then
|
||||
echo "ℹ️ Reusing open autofix PR scan for duplicate-PR annotation."
|
||||
elif ! gh pr list --repo "${REPO}" --state open --author "${AUTOFIX_BOT}" \
|
||||
--limit 100 --json number,headRefName,isCrossRepository > "${WORKDIR}/open-autofix-prs.json"; then
|
||||
echo "::warning::Open autofix PR scan failed; candidates will proceed without duplicate-PR annotation."
|
||||
else
|
||||
fi
|
||||
if [[ -s "${WORKDIR}/open-autofix-prs.json" ]]; then
|
||||
if ! jq -c --arg p "${BRANCH_PREFIX}" --slurpfile prs "${WORKDIR}/open-autofix-prs.json" '
|
||||
($prs[0] // []) as $prs
|
||||
| map(
|
||||
($p + (.number | tostring)) as $branch
|
||||
| (
|
||||
first($prs[] | select((.headRefName // "") == $branch) | {
|
||||
first($prs[] | select((.isCrossRepository != true) and ((.headRefName // "") == $branch)) | {
|
||||
number,
|
||||
headRefName
|
||||
}) // null
|
||||
|
|
@ -752,8 +791,8 @@ jobs:
|
|||
if: |-
|
||||
${{ steps.decision.outputs.go_issue != '' && needs.route.outputs.dry_run != 'true' }}
|
||||
env:
|
||||
# CI_DEV_BOT_PAT (the qwen-code-dev-bot PAT) opens the PR as
|
||||
# qwen-code-dev-bot. This is required: the default GITHUB_TOKEN is
|
||||
# CI_DEV_BOT_PAT opens the PR as the configured autofix bot. This is
|
||||
# required: the default GITHUB_TOKEN is
|
||||
# blocked from creating PRs ("GitHub Actions is not permitted to
|
||||
# create or approve pull requests"), and PRs it does create do not
|
||||
# trigger CI. The bot PAT clears both problems.
|
||||
|
|
@ -761,7 +800,7 @@ jobs:
|
|||
ISSUE: '${{ steps.decision.outputs.go_issue }}'
|
||||
run: |-
|
||||
if [[ -z "${GITHUB_TOKEN}" ]]; then
|
||||
echo '::error::CI_DEV_BOT_PAT is required to publish the PR as qwen-code-dev-bot.'
|
||||
echo '::error::CI_DEV_BOT_PAT is required to publish the PR as the autofix bot.'
|
||||
exit 1
|
||||
fi
|
||||
api_error_file="$(mktemp)"
|
||||
|
|
@ -849,8 +888,8 @@ jobs:
|
|||
fi
|
||||
|
||||
# ===========================================================================
|
||||
# REVIEW PHASE (scan) — find every autofix PR with new, unaddressed feedback
|
||||
# (or a base conflict) and emit them as a matrix. Cheap: GitHub API only.
|
||||
# REVIEW PHASE (scan) — find one autofix PR with new, unaddressed feedback
|
||||
# (or a base conflict) and emit it as a matrix. Cheap: GitHub API only.
|
||||
# ===========================================================================
|
||||
review-scan:
|
||||
needs: 'route'
|
||||
|
|
@ -876,22 +915,24 @@ jobs:
|
|||
# A forced PR must still pass these checks.
|
||||
if [[ -n "${FORCED_PR}" ]]; then
|
||||
META="$(gh pr view "${FORCED_PR}" --repo "${REPO}" \
|
||||
--json number,state,author,headRefName 2> /dev/null || echo '{}')"
|
||||
--json number,state,author,headRefName,isCrossRepository 2> /dev/null || echo '{}')"
|
||||
OK="$(jq -r --arg ab "${AUTOFIX_BOT}" --arg p "${BRANCH_PREFIX}" \
|
||||
'(((.state // "") == "OPEN")
|
||||
and ((.author.login // "") == $ab)
|
||||
and ((.isCrossRepository // true) == false)
|
||||
and ((.headRefName // "") | startswith($p)))' <<< "${META}")"
|
||||
if [[ "${OK}" != "true" ]]; then
|
||||
echo "❌ #${FORCED_PR} is not an open autofix PR owned by ${AUTOFIX_BOT}"
|
||||
echo "❌ #${FORCED_PR} is not an open in-repo autofix PR owned by ${AUTOFIX_BOT}"
|
||||
echo "targets=[]" >> "${GITHUB_OUTPUT}"
|
||||
echo "has_targets=false" >> "${GITHUB_OUTPUT}"
|
||||
exit 0
|
||||
fi
|
||||
CANDIDATES="${FORCED_PR}"
|
||||
else
|
||||
gh pr list --repo "${REPO}" --state open --author "${AUTOFIX_BOT}" \
|
||||
--limit 100 --json number,headRefName > "${WORKDIR}/bot-prs.json"
|
||||
--limit 100 --json number,headRefName,isCrossRepository > "${WORKDIR}/bot-prs.json"
|
||||
CANDIDATES="$(jq -r --arg p "${BRANCH_PREFIX}" \
|
||||
'.[] | select(.headRefName | startswith($p)) | .number' \
|
||||
'.[] | select((.isCrossRepository != true) and ((.headRefName // "") | startswith($p))) | .number' \
|
||||
"${WORKDIR}/bot-prs.json")"
|
||||
fi
|
||||
|
||||
|
|
@ -900,6 +941,18 @@ jobs:
|
|||
BRANCH="$(gh pr view "${PR}" --repo "${REPO}" --json headRefName --jq '.headRefName')"
|
||||
ISSUE="${BRANCH#"${BRANCH_PREFIX}"}"
|
||||
HEAD_SHA="$(gh api "repos/${REPO}/pulls/${PR}" --jq '.head.sha')"
|
||||
CHECKS_JSON="$(gh pr view "${PR}" --repo "${REPO}" \
|
||||
--json statusCheckRollup --jq '.statusCheckRollup // []' 2> /dev/null || echo '[]')"
|
||||
HAS_PENDING_CHECKS="$(jq -r '
|
||||
[ .[]
|
||||
| select((.status // .state // "") | IN("QUEUED", "IN_PROGRESS", "PENDING", "WAITING", "REQUESTED"))
|
||||
| select(((.workflowName // "") != "Qwen Autofix") or (((.name // "") | startswith("review-address")))) ]
|
||||
| length > 0
|
||||
' <<< "${CHECKS_JSON}")"
|
||||
if [[ "${HAS_PENDING_CHECKS}" == "true" ]]; then
|
||||
echo "⏳ #${PR}: PR has pending checks; skipping until the current verification finishes"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Push watermark: the PR's last push. Feedback older than this was in
|
||||
# front of the agent on a previous round.
|
||||
|
|
@ -960,6 +1013,7 @@ jobs:
|
|||
--arg round "${ROUND}" --arg wm "${EFF_WM}" \
|
||||
'. + [{pr: $pr, branch: $branch, issue: $issue, round: $round, watermark: $wm}]' \
|
||||
<<< "${TARGETS}")"
|
||||
break # one PR per scheduled scan
|
||||
done
|
||||
|
||||
COUNT="$(jq 'length' <<< "${TARGETS}")"
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ blocked, write `<workdir>/failure.md` and do not commit.
|
|||
|
||||
Inputs: `--pr`, `--issue`, `<workdir>/feedback.md`, `--conflict`, and `--base`.
|
||||
|
||||
The workflow already checked out `autofix/issue-<issue>`. Stay on that branch.
|
||||
The workflow already checked out the PR's head branch. Stay on that branch.
|
||||
Read `git diff origin/<base>...HEAD` first, then `<workdir>/feedback.md`.
|
||||
|
||||
Classify every feedback point:
|
||||
|
|
|
|||
50
docs/plans/2026-07-08-autofix-single-target-scheduler.md
Normal file
50
docs/plans/2026-07-08-autofix-single-target-scheduler.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Autofix Single Target Scheduler Implementation Plan
|
||||
|
||||
**Goal:** Make the Qwen Autofix workflow run every 10 minutes, process at most one actionable autofix PR per scan, react once to submitted reviews on bot PRs, and fall back to one existing approved issue only when no PR needs work.
|
||||
|
||||
**Architecture:** Keep the existing `qwen-autofix.yml` lifecycle and agent modes. Tighten the route and scan jobs so scheduled runs always scan review PRs first, issue work only proceeds when review scan has no target, and PR handling is still locked per PR.
|
||||
|
||||
**Tech Stack:** GitHub Actions YAML, `gh` CLI, `jq`, Vitest string guards in `scripts/tests/qwen-autofix-workflow.test.js`.
|
||||
|
||||
### Task 1: Add Workflow Guard Tests
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `scripts/tests/qwen-autofix-workflow.test.js`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Add tests for `AUTOFIX_BOT` using `vars.AUTOFIX_BOT_LOGIN`.
|
||||
2. Add tests for `*/10 * * * *` schedule and no 4-hour schedule.
|
||||
3. Add tests that submitted review events route only for bot PRs.
|
||||
4. Add tests that review scan emits at most one target and skips PRs with pending checks.
|
||||
5. Add tests that issue phase waits for review scan to have no target and respects a WIP PR cap.
|
||||
6. Run `npx vitest run scripts/tests/qwen-autofix-workflow.test.js` and confirm failure.
|
||||
|
||||
### Task 2: Update Workflow Routing and Scan
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `.github/workflows/qwen-autofix.yml`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Change scheduled cron to 10 minutes.
|
||||
2. Change `AUTOFIX_BOT` to use `vars.AUTOFIX_BOT_LOGIN`.
|
||||
3. Raise `MAX_ROUNDS`.
|
||||
4. Add `MAX_OPEN_AUTOFIX_PRS`.
|
||||
5. Add a low-frequency `pull_request_review: submitted` trigger for bot PRs.
|
||||
6. Make `issue-autofix` depend on `review-scan` for scheduled review-first fallback.
|
||||
7. Make review scan pick one target and skip PRs with pending checks.
|
||||
8. Gate issue candidate scan when review scan has a target or open autofix PR count exceeds the WIP cap.
|
||||
|
||||
### Task 3: Verify
|
||||
|
||||
**Files:**
|
||||
|
||||
- Test: `scripts/tests/qwen-autofix-workflow.test.js`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Run `npx vitest run scripts/tests/qwen-autofix-workflow.test.js`.
|
||||
2. Inspect the diff for unrelated changes.
|
||||
|
|
@ -32,6 +32,11 @@ const routeStep =
|
|||
workflow.match(
|
||||
/- name: 'Decide phases'[\s\S]*?(?=\n[ ]{2}# ==========)/,
|
||||
)?.[0] ?? '';
|
||||
const reviewScanJob =
|
||||
workflow.match(/\n {2}review-scan:[\s\S]*?(?=\n[ ]{2}# ==========)/)?.[0] ?? '';
|
||||
const issueAutofixJob =
|
||||
workflow.match(/\n {2}issue-autofix:[\s\S]*?(?=\n[ ]{2}# ==========)/)?.[0] ??
|
||||
'';
|
||||
const publishPrStep =
|
||||
workflow.match(
|
||||
/- name: 'Publish PR'[\s\S]*?(?=\n[ ]{6}- name: 'Withdraw claim on failure')/,
|
||||
|
|
@ -191,6 +196,63 @@ describe('qwen-autofix workflow', () => {
|
|||
expect(workflow).toContain('.[0:10] | map(. + {autofixTier: 1})');
|
||||
});
|
||||
|
||||
it('runs scheduled autofix as a 10-minute single-target worker', () => {
|
||||
expect(workflow).toContain("cron: '*/10 * * * *'");
|
||||
expect(workflow).not.toContain("cron: '0 0,12 * * *'");
|
||||
expect(workflow).not.toContain("cron: '0 4,8,16,20 * * *'");
|
||||
expect(workflow).toContain(
|
||||
"pull_request_review:\n types:\n - 'submitted'",
|
||||
);
|
||||
expect(workflow).toContain(
|
||||
'AUTOFIX_BOT: "${{ vars.AUTOFIX_BOT_LOGIN || \'qwen-code-dev-bot\' }}"',
|
||||
);
|
||||
expect(workflow).toContain("MAX_ROUNDS: '5'");
|
||||
expect(workflow).toContain("MAX_OPEN_AUTOFIX_PRS: '5'");
|
||||
expect(reviewScanJob).toContain('isCrossRepository');
|
||||
expect(reviewScanJob).toContain('not an open in-repo autofix PR');
|
||||
expect(reviewScanJob).toContain('.isCrossRepository != true');
|
||||
expect(reviewScanJob).toContain('break # one PR per scheduled scan');
|
||||
expect(reviewScanJob).toContain('statusCheckRollup');
|
||||
expect(reviewScanJob).toContain('HAS_PENDING_CHECKS');
|
||||
expect(reviewScanJob).toContain('.status // .state // ""');
|
||||
expect(reviewScanJob).toContain('.workflowName // ""');
|
||||
expect(reviewScanJob).toContain('startswith("review-address")');
|
||||
expect(reviewScanJob).toContain('echo "targets=[]" >> "${GITHUB_OUTPUT}"');
|
||||
expect(reviewScanJob).toContain(
|
||||
'PR has pending checks; skipping until the current verification finishes',
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to existing issue backlog only when review has no target', () => {
|
||||
expect(issueAutofixJob).toContain("needs: ['route', 'review-scan']");
|
||||
expect(issueAutofixJob).toContain('always()');
|
||||
expect(issueAutofixJob).toContain("needs.review-scan.result == 'success'");
|
||||
expect(issueAutofixJob).toContain(
|
||||
"github.event_name != 'schedule' || (needs.review-scan.result == 'success' && needs.review-scan.outputs.has_targets != 'true')",
|
||||
);
|
||||
expect(findCandidateIssuesStep).toContain('OPEN_AUTOFIX_PR_COUNT');
|
||||
expect(findCandidateIssuesStep).toContain('MAX_OPEN_AUTOFIX_PRS');
|
||||
expect(findCandidateIssuesStep).toContain('isCrossRepository');
|
||||
expect(findCandidateIssuesStep).toContain(
|
||||
'open autofix PR(s) already exist; WIP limit is ${MAX_OPEN_AUTOFIX_PRS}',
|
||||
);
|
||||
});
|
||||
|
||||
it('routes submitted review events only for bot PRs', () => {
|
||||
expect(routeStep).toContain('PR_AUTHOR');
|
||||
expect(routeStep).toContain('PR_NUMBER_EVENT');
|
||||
expect(routeStep).toContain(
|
||||
'if [[ "${EVENT_NAME}" == \'pull_request_review\' ]]; then',
|
||||
);
|
||||
expect(routeStep).toContain('if [[ "${PR_AUTHOR}" == "${AUTOFIX_BOT}" ]]');
|
||||
expect(routeStep).toContain(
|
||||
'ROUTE_PR="$(sanitize_number "${PR_NUMBER_EVENT}")',
|
||||
);
|
||||
expect(routeStep).toContain(
|
||||
"review event ignored: PR author '${PR_AUTHOR:-n/a}' is not ${AUTOFIX_BOT}",
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps label-triggered issue routing guarded and diagnosable', () => {
|
||||
expect(workflow).toContain("issues:\n types:\n - 'labeled'");
|
||||
expect(workflow).toContain(
|
||||
|
|
@ -273,9 +335,6 @@ describe('qwen-autofix workflow', () => {
|
|||
expect(workflow).not.toContain(
|
||||
"pull_request_review_comment:\n types:\n - 'created'",
|
||||
);
|
||||
expect(workflow).not.toContain(
|
||||
"pull_request_review:\n types:\n - 'submitted'",
|
||||
);
|
||||
expect(workflow).not.toContain(
|
||||
"COMMENT_BODY: '${{ github.event.comment.body }}'",
|
||||
);
|
||||
|
|
@ -342,7 +401,7 @@ describe('qwen-autofix workflow', () => {
|
|||
'($p + (.number | tostring)) as $branch',
|
||||
);
|
||||
expect(findCandidateIssuesStep).toContain(
|
||||
'first($prs[] | select((.headRefName // "") == $branch)',
|
||||
'first($prs[] | select((.isCrossRepository != true) and ((.headRefName // "") == $branch))',
|
||||
);
|
||||
expect(findCandidateIssuesStep).toContain('existingAutofixPr');
|
||||
expect(findCandidateIssuesStep).toContain('annotated-candidates.json');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue