From b442b957280a6012149b98e7338fb276e7b68f5d Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sun, 31 May 2026 06:17:28 -0700 Subject: [PATCH] feat(agent-queue): per-repo verify + opt-in auto-merge for PR jobs Claim now carries verify (drives the existing verify gate -> PR opens only if it passes) and autoMerge (squash-merge via gh pr merge after the PR opens, non-fatal). selftest covers both. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- agent-queue/agent-queue.sh | 16 +++++++++++++++- agent-queue/lib/fleet-client.sh | 7 ++++++- agent-queue/selftest.sh | 14 +++++++++----- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/agent-queue/agent-queue.sh b/agent-queue/agent-queue.sh index ee90292..1a3ceec 100755 --- a/agent-queue/agent-queue.sh +++ b/agent-queue/agent-queue.sh @@ -709,6 +709,17 @@ _fleet_pr_open() { printf '%s' "$url" | tr -d '\n' } +# Auto-merge a PR (opt-in via the job's autoMerge flag). Squash-merges and deletes +# the branch; non-fatal — a failure (e.g. branch protection / required checks) leaves +# the PR open. Returns 0 on merge. +_fleet_pr_merge() { + local dir=$1 url=$2 logf=$3 + if ( cd "$dir" && "$GH_BIN" pr merge "$url" --squash --delete-branch >>"$logf" 2>&1 ); then + echo "PR auto-merged: $url" >>"$logf"; return 0 + fi + echo "PR: auto-merge failed — left open: $url" >>"$logf"; return 1 +} + # ── Worker: runs one job to completion (invoked in background) ─────── run_worker() { local doing_file=$1 @@ -796,10 +807,11 @@ run_worker() { # agent in an isolated checkout on branch aq/job/ instead of the static # cwd. The PR is opened after a passing verify. WIP checkpointing is skipped # for PR jobs — the pushed PR branch is the durable artifact. ── - local pr_dir="" pr_base="" pr_repo="" pr_jid="" + local pr_dir="" pr_base="" pr_repo="" pr_jid="" pr_automerge="" if [[ "${AQ_FLEET_PR:-0}" == 1 ]] && fleet_enabled && _fleet_is_job "$job"; then pr_repo=$(fm_get "$doing_file" fleet-repo "") pr_base=$(fm_get "$doing_file" fleet-base-branch "main") + pr_automerge=$(fm_get "$doing_file" fleet-automerge "") # Branch off the stable fleet job id (not the transient local job name). pr_jid=$(fm_get "$doing_file" fleet-job-id "$job") if [[ -n "$pr_repo" ]]; then @@ -966,6 +978,8 @@ run_worker() { if [[ -n "$_prurl" ]]; then { echo "pr_url=$_prurl"; echo "pr_branch=aq/job/$pr_jid"; } >> "$metaf" echo "PR opened: $_prurl" >> "$logf" + # Opt-in auto-merge (job autoMerge flag): squash-merge the PR now. + [[ "$pr_automerge" == "true" ]] && _fleet_pr_merge "$pr_dir" "$_prurl" "$logf" if fleet_enabled && _fleet_is_job "$job"; then fleet_report_insights "$job" testing; fi fi fi diff --git a/agent-queue/lib/fleet-client.sh b/agent-queue/lib/fleet-client.sh index 8de5532..0058d3d 100644 --- a/agent-queue/lib/fleet-client.sh +++ b/agent-queue/lib/fleet-client.sh @@ -185,12 +185,14 @@ fleet_claim() { case "$FLEET_CODE" in 2*) :;; *) err "fleet: claim failed (HTTP ${FLEET_CODE:-error})"; return 1;; esac printf '%s' "$FLEET_BODY" | grep -q '"claimed"[[:space:]]*:[[:space:]]*true' || return 2 - local jid body_md epoch repo base_branch + local jid body_md epoch repo base_branch verify automerge="" jid=$(printf '%s' "$FLEET_BODY" | _json_str id) body_md=$(printf '%s' "$FLEET_BODY" | _json_str bodyMd) epoch=$(printf '%s' "$FLEET_BODY" | _fleet_json_num leaseEpoch) repo=$(printf '%s' "$FLEET_BODY" | _json_str repo) base_branch=$(printf '%s' "$FLEET_BODY" | _json_str baseBranch) + verify=$(printf '%s' "$FLEET_BODY" | _json_str verify) + printf '%s' "$FLEET_BODY" | grep -q '"autoMerge"[[:space:]]*:[[:space:]]*true' && automerge=true [[ -n "$jid" ]] || { err "fleet: claim returned no job id"; return 1; } # Materialize a transient local job .md (same approach as from-tracker) so the @@ -208,6 +210,9 @@ fleet_claim() { echo "fleet-lease-epoch: ${epoch:-0}" [[ -n "$repo" ]] && echo "fleet-repo: $repo" [[ -n "$base_branch" ]] && echo "fleet-base-branch: $base_branch" + # Per-repo verify command (drives the existing verify gate) + auto-merge flag. + [[ -n "$verify" ]] && echo "verify: $verify" + [[ -n "$automerge" ]] && echo "fleet-automerge: true" echo "idempotency-key: fleet-$jid" echo "---" echo diff --git a/agent-queue/selftest.sh b/agent-queue/selftest.sh index f3cdaf3..ac759f7 100755 --- a/agent-queue/selftest.sh +++ b/agent-queue/selftest.sh @@ -775,6 +775,8 @@ case "$1 $2" in [ -n "${AQ_FSTUB_CLAIM_FLAG:-}" ] && : > "$AQ_FSTUB_CLAIM_FLAG" repo_field="" [ -n "${AQ_FSTUB_REPO:-}" ] && repo_field=",\"repo\":\"${AQ_FSTUB_REPO}\",\"baseBranch\":\"${AQ_FSTUB_BASE:-main}\"" + [ -n "${AQ_FSTUB_VERIFY:-}" ] && repo_field="${repo_field},\"verify\":\"${AQ_FSTUB_VERIFY}\"" + [ "${AQ_FSTUB_AUTOMERGE:-}" = "1" ] && repo_field="${repo_field},\"autoMerge\":true" printf '{"claimed":true,"job":{"id":"%s","bodyMd":"%s","leaseEpoch":1%s},"lease":{"leaseEpoch":1}}\n200\n' \ "${AQ_FSTUB_JOB_ID:-fjob_1}" "${AQ_FSTUB_BODY:-do the work}" "$repo_field" fi ;; @@ -877,23 +879,25 @@ printf '#!/usr/bin/env bash\necho "$@" >> "%s"\necho "https://github.com/test/re export AGENT_QUEUE_ROOT="$tmp/queue-fl-pr"; export AQ_FLEET_CWD="$work" "$AQ" init >/dev/null export AQ_FSTUB_CALLS="$tmp/fl-pr-calls.log" AQ_FSTUB_CLAIM_FLAG="$tmp/fl-pr-claimed" \ - AQ_FSTUB_JOB_ID="fjob_pr" AQ_FSTUB_BODY="add a file" AQ_FSTUB_REPO="$prbare" AQ_FSTUB_BASE="main" + AQ_FSTUB_JOB_ID="fjob_pr" AQ_FSTUB_BODY="add a file" AQ_FSTUB_REPO="$prbare" AQ_FSTUB_BASE="main" \ + AQ_FSTUB_VERIFY="true" AQ_FSTUB_AUTOMERGE="1" : > "$AQ_FSTUB_CALLS"; rm -f "$AQ_FSTUB_CLAIM_FLAG" -AQ_FLEET=1 AQ_FLEET_PR=1 AQ_FLEET_AUTOSHIP=1 AGENT_QUEUE_VERIFY=true AGENT_QUEUE_POLL=1 \ +AQ_FLEET=1 AQ_FLEET_PR=1 AQ_FLEET_AUTOSHIP=1 AGENT_QUEUE_POLL=1 \ AQ_FLEET_REPOS_DIR="$tmp/pr-repos" GH_BIN="$ghstub" DEVIN_BIN="$prengine" "$AQ" run --once >/dev/null 2>&1 if git -C "$prbare" rev-parse --verify aq/job/fjob_pr >/dev/null 2>&1 \ && grep -q -- '--title Add smoke marker file' "$tmp/gh-calls.log" 2>/dev/null \ + && grep -q 'pr merge' "$tmp/gh-calls.log" 2>/dev/null \ && git -C "$prbare" ls-tree -r aq/job/fjob_pr --name-only 2>/dev/null | grep -qx 'PR_CHANGE.md' \ && ! git -C "$prbare" ls-tree -r aq/job/fjob_pr --name-only 2>/dev/null | grep -qx '.aq_pr.md' \ && grep -q '/fleet/jobs/fjob_pr/lease/release :: .*"prUrl":"https://github.com/test/repo/pull/7"' "$AQ_FSTUB_CALLS"; then - pass "fleet PR mode: agent-authored PR title used + .aq_pr.md not committed + prUrl reported" + pass "fleet PR mode: authored title + verify gate + auto-merge (gh pr merge) + prUrl reported" else echo "gh-calls: $(cat "$tmp/gh-calls.log" 2>/dev/null)" >&2 echo "tree: $(git -C "$prbare" ls-tree -r aq/job/fjob_pr --name-only 2>/dev/null | tr '\n' ' ')" >&2 grep release "$AQ_FSTUB_CALLS" >&2 2>/dev/null - fail "PR mode did not push branch / use authored title / report prUrl" + fail "PR mode did not push branch / authored title / auto-merge / report prUrl" fi -unset AQ_FSTUB_REPO AQ_FSTUB_BASE +unset AQ_FSTUB_REPO AQ_FSTUB_BASE AQ_FSTUB_VERIFY AQ_FSTUB_AUTOMERGE # 36. FENCING: PATCH returns conflict (stale epoch) → worker self-aborts, job is # quarantined to failed/ (NOT review/testing/shipped), fenced is recorded.