@lead: issue #16 — fully-automatic autopilot (label-gated @pm auto-delegate + @qa merge)
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
# Required env (provided by the workflow step):
|
||||
# GT AGENT_TOKEN TOKEN_PM TOKEN_SENIOR TOKEN_JUNIOR TOKEN_LEAD TOKEN_QA
|
||||
# NAME MODE NUM TITLE BRANCH NEW GITHUB_SERVER_URL GITHUB_REPOSITORY
|
||||
# IS_PR AUTOPILOT ISSNUM (autopilot: @qa label-gated merge/halt + auto-trigger @qa on a fresh PR)
|
||||
set +e # publish is best-effort: a grep-no-match / curl non-zero must NOT kill the step
|
||||
# Post/PR as the agent's OWN Gitea user when its token is configured; else the built-in bot.
|
||||
case "$NAME" in
|
||||
@@ -17,14 +18,30 @@ API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||
hdr=(-H "Authorization: token $TOK" -H "Content-Type: application/json")
|
||||
post() { curl -sS -w 'comment -> HTTP %{http_code}\n' -X POST "${hdr[@]}" \
|
||||
"$API/issues/$NUM/comments" -d "$(jq -nc --arg b "$1" '{body:$b}')"; }
|
||||
# Remove the 'autopilot' label from an issue by resolving its ID first (Gitea's DELETE label
|
||||
# endpoint is by ID, not name). Arg $1 = issue number. Used as the autopilot kill switch.
|
||||
del_autopilot_label() {
|
||||
local iss="$1" lid
|
||||
lid=$(curl -sS "${hdr[@]}" "$API/issues/$iss/labels" 2>/dev/null \
|
||||
| jq -r 'if type=="array" then ([.[]|select(.name=="autopilot")][0].id // empty) else empty end')
|
||||
if [ -n "$lid" ]; then
|
||||
curl -sS -X DELETE "${hdr[@]}" "$API/issues/$iss/labels/$lid" \
|
||||
-w '\nunlabel -> HTTP %{http_code}\n' || true
|
||||
else
|
||||
echo "no 'autopilot' label found on #$iss to remove"
|
||||
fi
|
||||
}
|
||||
|
||||
# drop machine-readable markers: DELEGATE / CLOSE_ISSUE, and the BEGIN_SUBTASKS..END_SUBTASKS and
|
||||
# BEGIN_PR_DESCRIPTION..END_PR_DESCRIPTION blocks (the PR description is published separately).
|
||||
# drop machine-readable markers: DELEGATE / CLOSE_ISSUE / MERGE_PR / HALT_AUTOPILOT, and the
|
||||
# BEGIN_SUBTASKS..END_SUBTASKS and BEGIN_PR_DESCRIPTION..END_PR_DESCRIPTION blocks (the PR
|
||||
# description is published separately).
|
||||
reply=$(awk '
|
||||
/^[[:space:]]*BEGIN_SUBTASKS/{s=1}
|
||||
/^[[:space:]]*BEGIN_PR_DESCRIPTION/{p=1}
|
||||
/^[[:space:]]*DELEGATE:[[:space:]]*@/{next}
|
||||
/^[[:space:]]*CLOSE_ISSUE[[:space:]]*$/{next}
|
||||
/^[[:space:]]*MERGE_PR[[:space:]]*$/{next}
|
||||
/^[[:space:]]*HALT_AUTOPILOT[[:space:]]*$/{next}
|
||||
s{ if(/^[[:space:]]*END_SUBTASKS/){s=0}; next }
|
||||
p{ if(/^[[:space:]]*END_PR_DESCRIPTION/){p=0}; next }
|
||||
{print}
|
||||
@@ -91,6 +108,43 @@ if [ "$MODE" != "pr" ]; then
|
||||
subtext=$(printf '\n\n---\n🤖 **@%s** — created sub-issues%s (mention an agent on each when ready):%b' "$NAME" "${ms:+ under milestone **$ms**}" "$links")
|
||||
fi
|
||||
post "$(printf '🤖 **@%s**\n\n%s%s' "$NAME" "$msg" "$subtext")"
|
||||
|
||||
# --- AUTOPILOT: @qa's narrow, label-gated merge / halt authority ---
|
||||
# Only @qa, only when 'autopilot' is set, and only on a PR thread. The MERGE_PR / HALT_AUTOPILOT
|
||||
# markers come from the QA prompt. Merge + label ops use TOKEN_QA (the QA user's PAT, which the
|
||||
# maintainer must grant write+merge scope). ISSNUM is the origin issue (resolved from the branch).
|
||||
if [ "$NAME" = "qa" ] && [ "$AUTOPILOT" = "true" ]; then
|
||||
if grep -qiE '^[[:space:]]*MERGE_PR[[:space:]]*$' /tmp/agent_out.md; then
|
||||
if [ -z "$IS_PR" ]; then
|
||||
echo "MERGE_PR marker but this run is not on a PR thread — skipping merge"
|
||||
else
|
||||
echo "@qa autopilot: merging PR #$NUM (origin issue #${ISSNUM:-$NUM})"
|
||||
mc=$(curl -sS -o /tmp/merge_resp.txt -w '%{http_code}' -X POST "${hdr[@]}" \
|
||||
"$API/pulls/$NUM/merge" -d '{"Do":"merge"}')
|
||||
echo "merge -> HTTP $mc"; cat /tmp/merge_resp.txt 2>/dev/null || true
|
||||
case "$mc" in
|
||||
200|201|204)
|
||||
echo "closing origin issue #${ISSNUM:-$NUM}"
|
||||
curl -sS -X PATCH "${hdr[@]}" "$API/issues/${ISSNUM:-$NUM}" \
|
||||
-d '{"state":"closed"}' -w '\nclose -> HTTP %{http_code}\n' || true
|
||||
post "$(printf '🤖 **@qa** — ✅ verified & merged PR #%s (autopilot). Closed issue #%s.' "$NUM" "${ISSNUM:-$NUM}")"
|
||||
;;
|
||||
*)
|
||||
# Merge failed (checks not green, conflicts, or TOKEN_QA lacks merge scope) — do NOT
|
||||
# silently proceed: drop the label so it reverts to human control and report.
|
||||
del_autopilot_label "${ISSNUM:-$NUM}"
|
||||
post "$(printf '🤖 **@qa** — ⚠️ tried to merge PR #%s but the API returned HTTP %s (checks not green, a conflict, or missing merge permission on TOKEN_QA). Removed the `autopilot` label — @ffaerber please take a look.' "$NUM" "$mc")"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
elif grep -qiE '^[[:space:]]*HALT_AUTOPILOT[[:space:]]*$' /tmp/agent_out.md; then
|
||||
echo "@qa autopilot: HALT — removing 'autopilot' label from #${ISSNUM:-$NUM}"
|
||||
del_autopilot_label "${ISSNUM:-$NUM}"
|
||||
post "$(printf '🤖 **@qa** — 🛑 found a problem, so I did NOT merge. Removed the `autopilot` label (back to human control). @ffaerber please decide next steps (details above).')"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Auto-delegate: if the plan names a teammate, trigger them via AGENT_TOKEN (a PAT, so it
|
||||
# fires a new workflow run — the built-in token cannot). Never targets @pm or self, so the
|
||||
# chain always terminates at a dev. The '🤖' guard on the trigger stops status-comment loops.
|
||||
@@ -185,6 +239,17 @@ prpost() {
|
||||
|
||||
if [ "$NEW" = "true" ]; then
|
||||
prpost "$prnum" "$(printf '🤖 **@%s** — ✅ PR ready for review — @ffaerber please review & merge:\n- %s%s' "$NAME" "$url" "$activity")"
|
||||
# AUTOPILOT: hand the fresh PR to @qa automatically (via AGENT_TOKEN, so it fires a new run).
|
||||
# @qa then verifies and — if green — merges + closes via its MERGE_PR marker. The comment lands
|
||||
# on the PR thread ($prnum) so the next run resolves the origin issue's label from the branch
|
||||
# name. The '🤖' guard on the trigger gate stops status-comment loops.
|
||||
if [ "$AUTOPILOT" = "true" ] && [ -n "$AGENT_TOKEN" ] && [ -n "$prnum" ]; then
|
||||
echo "autopilot: auto-triggering @qa to review PR #$prnum"
|
||||
curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" -H "Content-Type: application/json" \
|
||||
"$API/issues/$prnum/comments" \
|
||||
-d "$(jq -nc --arg b "@qa please verify this PR (autopilot: issue #$NUM is labeled autopilot). Merge it if correct, or halt and remove the label if you find a problem." '{body:$b}')" \
|
||||
-w '\ntrigger-qa -> HTTP %{http_code}\n' || true
|
||||
fi
|
||||
else
|
||||
# Resume: just link the PR — its body and the diff already carry the description, so we don't
|
||||
# repeat the full write-up in the comment (the reasoning trail below shows what this run did).
|
||||
|
||||
Reference in New Issue
Block a user