From 84cec444eff4e3b483021bf192b667f724cf1012 Mon Sep 17 00:00:00 2001 From: Felix Faerber Date: Sun, 5 Jul 2026 21:06:39 +0300 Subject: [PATCH] =?UTF-8?q?agents:=20PM-orchestrated=20flow=20=E2=80=94=20?= =?UTF-8?q?@pm=20hub,=20@qa=20reviews,=20human/@pm=20merges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework the delegation/trigger choreography so @pm orchestrates end-to-end from the issue thread while @qa reviews on the PR and never merges. Flow: issue → @pm plans (asks creator; autopilot skips) → dev builds PR → dev pings @pm on the issue → @pm hands to @qa → @qa reviews ON THE PR (recommends / BOUNCE:@dev, direct qa↔dev loop, 3 max) → @qa APPROVEs → verdict on the ISSUE → @pm → normal: tells creator "ready to merge" (human merges); autopilot: @pm MERGE_PR. @pm never reads or comments on the PR (small context); @qa never merges. - run-agent.sh: @pm prompt = 3 phases (plan / hand PR to @qa / finalize), autopilot only drops the approval gate + adds @pm MERGE_PR. @qa prompt = reviewer, ends with APPROVE / BOUNCE:@dev / HALT; identical in both flows. - publish.sh: shared post_to()/trig()/resolve_pr() helpers + ISSN; @qa routes reply to PR (bounce) vs issue (approve) and fires @pm/@dev; @pm handles DELEGATE:@dev, DELEGATE:@qa (resolves the PR), and autopilot MERGE_PR; dev NEW=true fires @pm on the issue, NEW=false fires @qa on the PR. Strip APPROVE/HALT markers. Tokens re-minted to match: TOKEN_PM gains write:repository (it merges now), TOKEN_QA drops to read:repository (never merges). Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitea/workflows/scripts/publish.sh | 204 ++++++++++++-------------- .gitea/workflows/scripts/run-agent.sh | 85 ++++++----- 2 files changed, 141 insertions(+), 148 deletions(-) diff --git a/.gitea/workflows/scripts/publish.sh b/.gitea/workflows/scripts/publish.sh index 89bf1bb..08320ce 100755 --- a/.gitea/workflows/scripts/publish.sh +++ b/.gitea/workflows/scripts/publish.sh @@ -32,6 +32,20 @@ hdr=(-H "Authorization: token $TOK" -H "Content-Type: application/json") MARK=$'\n\n' post() { curl -sS -w 'comment -> HTTP %{http_code}\n' -X POST "${hdr[@]}" \ "$API/issues/$NUM/comments" -d "$(jq -nc --arg b "$1$MARK" '{body:$b}')"; } +# Post a MARKED status/reply comment to an ARBITRARY thread (issue or PR) — never fires a run. +post_to() { curl -sS -w "comment(#$1) -> HTTP %{http_code}\n" -X POST "${hdr[@]}" \ + "$API/issues/$1/comments" -d "$(jq -nc --arg b "$2$MARK" '{body:$b}')"; } +# Post an UNMARKED TRIGGER comment on a thread — fires the mentioned agent's next run. Must use a PAT +# (TTOK); the built-in GITEA_TOKEN cannot start new runs. No-op (logged) if this agent has no PAT. +trig() { if [ -z "$TTOK" ]; then echo "no trigger token — cannot fire on #$1"; return; fi + curl -sS -w "trigger(#$1) -> HTTP %{http_code}\n" -X POST \ + -H "Authorization: token $TTOK" -H "Content-Type: application/json" \ + "$API/issues/$1/comments" -d "$(jq -nc --arg b "$2" '{body:$b}')"; } +# Origin issue for this run (route.sh resolves it from the branch on PR threads), and a resolver for +# the open PR built from its branch (ai/issue-). Lets @pm/@qa cross between the issue and PR. +ISSN="${ISSNUM:-$NUM}" +resolve_pr() { curl -sS "${hdr[@]}" "$API/pulls?state=open&limit=50" \ + | jq -r --arg br "ai/issue-$ISSN" 'if type=="array" then (map(select(.head.ref==$br))|.[0].number // empty) else empty end' 2>/dev/null; } # 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() { @@ -46,7 +60,7 @@ del_autopilot_label() { fi } -# drop machine-readable markers: DELEGATE / CLOSE_ISSUE / MERGE_PR / HALT_AUTOPILOT, and the +# drop machine-readable markers: DELEGATE / CLOSE_ISSUE / MERGE_PR / APPROVE / HALT / BOUNCE, and the # BEGIN_SUBTASKS..END_SUBTASKS and BEGIN_PR_DESCRIPTION..END_PR_DESCRIPTION blocks (the PR # description is published separately). reply=$(awk ' @@ -55,7 +69,8 @@ reply=$(awk ' /^[[:space:]]*DELEGATE:[[:space:]]*@/{next} /^[[:space:]]*CLOSE_ISSUE[[:space:]]*$/{next} /^[[:space:]]*MERGE_PR[[:space:]]*$/{next} - /^[[:space:]]*HALT_AUTOPILOT[[:space:]]*$/{next} + /^[[:space:]]*APPROVE[[:space:]]*$/{next} + /^[[:space:]]*HALT([_ ]AUTOPILOT)?[[:space:]]*$/{next} /^[[:space:]]*BOUNCE:[[:space:]]*@/{next} s{ if(/^[[:space:]]*END_SUBTASKS/){s=0}; next } p{ if(/^[[:space:]]*END_PR_DESCRIPTION/){p=0}; next } @@ -81,6 +96,39 @@ prdesc=$(awk '/BEGIN_PR_DESCRIPTION/{f=1;next} /END_PR_DESCRIPTION/{f=0} f' /tmp if [ "$MODE" != "pr" ]; then git checkout -- . 2>/dev/null || true git clean -fd 2>/dev/null || true + + # ---------- @qa: reviewer only — never edits, never merges ---------- + # Recommendations land ON THE PR (onsite the diff); the pass/fail verdict lands ON THE ISSUE so + # @pm (who never reads the PR) can act on it. Ends its reply with APPROVE / BOUNCE: @dev / HALT. + if [ "$NAME" = "qa" ]; then + PRN=$(resolve_pr) + if grep -qiE '^[[:space:]]*APPROVE[[:space:]]*$' /tmp/agent_out.md; then + post_to "$ISSN" "$(printf '✅ Reviewed PR #%s — looks good.\n\n%s' "${PRN:-?}" "$reply")" + trig "$ISSN" "@pm — @qa approved PR #${PRN:-?} (issue #$ISSN). Over to you." + elif grep -qiE '^[[:space:]]*BOUNCE:[[:space:]]*@(junior|senior|lead)' /tmp/agent_out.md; then + dev=$(grep -oiE 'BOUNCE:[[:space:]]*@(junior|senior|lead)' /tmp/agent_out.md | head -1 | grep -oiE '(junior|senior|lead)' | tr '[:upper:]' '[:lower:]') + [ -z "$dev" ] && [ -n "$PRN" ] && dev=$(curl -sS "${hdr[@]}" "$API/pulls/$PRN" | jq -r '.user.login // "junior"') + dest="${PRN:-$NUM}" + post_to "$dest" "$reply" # recommendations, on the PR + # Bounce budget: count prior "fix attempt" markers on the PR thread; stop after 3. + prior=$(curl -sS "${hdr[@]}" "$API/issues/$dest/comments?limit=100" | jq -r 'if type=="array" then [.[]|select(.body|test("fix attempt"))]|length else 0 end' 2>/dev/null); prior=${prior:-0} + if [ "$prior" -ge 3 ]; then + [ "$AUTOPILOT" = "true" ] && del_autopilot_label "$ISSN" + post_to "$ISSN" "🛑 Still not right after 3 fix attempts on PR #${PRN:-?} — handing to @ffaerber (details on the PR)." + else + n=$((prior + 1)) + trig "$dest" "@${dev:-junior} please address @qa's review above and update PR #${PRN:-?} (fix attempt $n/3)." + fi + elif grep -qiE '^[[:space:]]*HALT([_ ]AUTOPILOT)?[[:space:]]*$' /tmp/agent_out.md; then + [ "$AUTOPILOT" = "true" ] && del_autopilot_label "$ISSN" + post_to "$ISSN" "$(printf '🛑 This needs a human decision (not a dev fix) — @ffaerber please take a look.\n\n%s' "$reply")" + else + post_to "${PRN:-$NUM}" "$reply" # no verdict yet (a question) — post where qa works + fi + exit 0 + fi + + # ---------- @pm / @ops: issue-thread orchestration ---------- target=$(grep -oiE 'DELEGATE:[[:space:]]*@(junior|senior|lead|qa)' /tmp/agent_out.md 2>/dev/null | head -1 | grep -oiE '(junior|senior|lead|qa)' | tr '[:upper:]' '[:lower:]') # Visible comment: the reply text, or a sensible line if the agent only emitted a marker. msg="$reply" @@ -127,91 +175,48 @@ if [ "$MODE" != "pr" ]; then fi post "$(printf '%s%s' "$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})" - # Merge with TOK (a PAT) — NOT the built-in Actions token — so the resulting push to - # main TRIGGERS downstream workflows (e.g. deploy). A merge made with the built-in GITEA_TOKEN - # does not fire new runs (loop-prevention), which silently skips the deploy. Fall back to the - # agent's own token only if TOK isn't set (then the deploy would need a manual run). - mtok="$TTOK" - mc=$(curl -sS -o /tmp/merge_resp.txt -w '%{http_code}' -X POST \ - -H "Authorization: token $mtok" -H "Content-Type: application/json" \ - "$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 '✅ 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 '⚠️ 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:]]*BOUNCE:[[:space:]]*@(junior|senior|lead)' /tmp/agent_out.md; then - # @qa wants the dev to fix something. Send it back — never fix it ourselves. After 3 bounces, - # stop and hand to the human. QA's feedback is already posted (the reply comment above). - if [ -z "$IS_PR" ]; then - echo "BOUNCE marker but this run is not on a PR thread — skipping" - else - target=$(grep -oiE 'BOUNCE:[[:space:]]*@(junior|senior|lead)' /tmp/agent_out.md | head -1 \ - | grep -oiE '(junior|senior|lead)' | tr '[:upper:]' '[:lower:]') - [ -z "$target" ] && target=$(curl -sS "${hdr[@]}" "$API/pulls/$NUM" | jq -r '.user.login // "junior"') - # Count how many times this PR has already been bounced (marker in the trigger comment). - prior=$(curl -sS "${hdr[@]}" "$API/issues/$NUM/comments?limit=100" \ - | jq -r 'if type=="array" then [.[]|select(.body|test("autopilot fix attempt"))]|length else 0 end' 2>/dev/null) - prior=${prior:-0} - if [ "$prior" -ge 3 ]; then - echo "@qa autopilot: 3 bounces already — halting" - del_autopilot_label "${ISSNUM:-$NUM}" - post "$(printf '🛑 Still not right after 3 fix attempts. Stopping autopilot (removed the `autopilot` label). @ffaerber please take over — details in the comments above.')" - else - n=$((prior + 1)) - echo "@qa autopilot: bounce $n/3 -> @$target" - curl -sS -X POST -H "Authorization: token $TTOK" -H "Content-Type: application/json" \ - "$API/issues/$NUM/comments" \ - -d "$(jq -nc --arg b "@$target please address @qa's feedback above and update this PR (autopilot fix attempt $n/3)." '{body:$b}')" \ - -w '\nbounce -> HTTP %{http_code}\n' || true - fi - 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 '🛑 This needs a human decision (not a dev fix). Removed the `autopilot` label (back to human control). @ffaerber please decide next steps (details above).')" + # --- @pm autopilot merge: @pm is the ONLY agent that merges, and ONLY under the autopilot label --- + # (@qa never merges — it approves and hands back here.) Merge with the PAT (TTOK), not the built-in + # token, so the push to main fires the deploy. TOKEN_PM must carry write:repository. + if [ "$NAME" = "pm" ] && [ "$AUTOPILOT" = "true" ] && grep -qiE '^[[:space:]]*MERGE_PR[[:space:]]*$' /tmp/agent_out.md; then + PRN=$(resolve_pr) + if [ -z "$PRN" ]; then + echo "MERGE_PR but no open PR found for issue #$ISSN" + else + echo "@pm autopilot: merging PR #$PRN (issue #$ISSN)" + mc=$(curl -sS -o /tmp/merge_resp.txt -w '%{http_code}' -X POST \ + -H "Authorization: token $TTOK" -H "Content-Type: application/json" \ + "$API/pulls/$PRN/merge" -d '{"Do":"merge"}') + echo "merge -> HTTP $mc"; cat /tmp/merge_resp.txt 2>/dev/null || true + case "$mc" in + 200|201|204) + curl -sS -X PATCH "${hdr[@]}" "$API/issues/$ISSN" -d '{"state":"closed"}' -w '\nclose -> HTTP %{http_code}\n' || true + post_to "$ISSN" "✅ Merged PR #$PRN (autopilot) and closed this issue." ;; + *) + del_autopilot_label "$ISSN" + post_to "$ISSN" "⚠️ Tried to merge PR #$PRN but the API returned HTTP $mc (checks not green, a conflict, or TOKEN_PM lacks merge scope). Removed the autopilot label — @ffaerber please take a look." ;; + esac fi exit 0 fi - # Auto-delegate: if the plan names a teammate, trigger them via TOK (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. - if [ -n "$TTOK" ]; then - # Only delegate on an explicit "DELEGATE: @" line — never on a prose mention, - # so an agent that is asking the maintainer a question does not hand off prematurely. - target=$(grep -oiE 'DELEGATE:[[:space:]]*@(junior|senior|lead|qa)' /tmp/agent_out.md 2>/dev/null \ - | head -1 | grep -oiE '(junior|senior|lead|qa)' | tr '[:upper:]' '[:lower:]') - if [ -n "$target" ] && [ "$target" != "$NAME" ]; then - echo "auto-delegating to @$target" - curl -sS -X POST -H "Authorization: token $TTOK" -H "Content-Type: application/json" \ - "$API/issues/$NUM/comments" \ - -d "$(jq -nc --arg b "@$target please proceed with issue #$NUM per the plan above (delegated by $NAME)." '{body:$b}')" \ - -w '\ndelegate -> HTTP %{http_code}\n' || true + # --- @pm delegation: hand the build to a dev, or hand the finished PR to @qa for review --- + # Only an explicit 'DELEGATE: @' line acts (never a prose mention). Fires via the PAT (TTOK) + # so a new run starts; the built-in token cannot. Everything posts on the ISSUE — @pm never touches + # the PR. Chain terminates: normal → @pm tells the creator (no marker); autopilot → @pm merges above. + if [ -n "$target" ] && [ "$target" != "$NAME" ]; then + if [ "$target" = "qa" ]; then + PRN=$(resolve_pr) + if [ -n "$PRN" ]; then + trig "$ISSN" "@qa please review PR #$PRN for issue #$ISSN — put your recommendations on the PR, or approve." + else + echo "DELEGATE:@qa but no open PR yet for issue #$ISSN — not firing" + fi else - echo "no DELEGATE marker — not delegating (agent is asking or finished)" + trig "$ISSN" "@$target please proceed with issue #$ISSN per the plan above (delegated by $NAME)." fi + else + echo "no DELEGATE marker — not delegating (agent is asking or finished)" fi exit 0 fi @@ -288,32 +293,15 @@ prpost() { } if [ "$NEW" = "true" ]; then - prpost "$prnum" "$(printf '✅ PR ready for review — @ffaerber please review & merge:\n- %s%s' "$url" "$activity")" - # AUTOPILOT: hand the fresh PR to @qa automatically (via TOK, 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 "$TTOK" ] && [ -n "$prnum" ]; then - echo "autopilot: auto-triggering @qa to review PR #$prnum" - curl -sS -X POST -H "Authorization: token $TTOK" -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 bounce it back to the dev with exactly what needs fixing." '{body:$b}')" \ - -w '\ntrigger-qa -> HTTP %{http_code}\n' || true - fi + # First PR for this issue: record it on the PR thread, then notify @pm on the ISSUE. @pm never + # reads the PR, so the issue gets only this one-line ping — @pm then routes it to @qa for review. + prpost "$prnum" "$(printf 'Opened PR #%s for review.%s' "$prnum" "$activity")" + trig "$ISSN" "@pm — PR #$prnum is ready for review (issue #$ISSN)." 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). - prpost "$prnum" "$(printf 'Pushed an update to the PR:\n- %s%s' "$url" "$activity")" - # AUTOPILOT: after a dev pushes a fix (e.g. following a @qa bounce), hand back to @qa to re-verify. - if [ "$AUTOPILOT" = "true" ] && [ -n "$TTOK" ] && [ -n "$prnum" ]; then - case "$NAME" in - junior|senior|lead) - echo "autopilot: dev pushed a fix — re-triggering @qa to re-verify PR #$prnum" - curl -sS -X POST -H "Authorization: token $TTOK" -H "Content-Type: application/json" \ - "$API/issues/$prnum/comments" \ - -d "$(jq -nc --arg b "@qa please re-verify this PR (autopilot). Merge it if now correct, or bounce it back with exactly what still needs fixing." '{body:$b}')" \ - -w '\ntrigger-qa -> HTTP %{http_code}\n' || true - ;; - esac - fi + # A fix (usually after a @qa bounce): update the PR and hand straight back to @qa to re-verify, + # on the PR thread. The qa↔dev loop is direct — it does NOT go back through @pm each round. + prpost "$prnum" "$(printf 'Pushed an update to PR #%s.%s' "$prnum" "$activity")" + case "$NAME" in + junior|senior|lead) trig "$prnum" "@qa please re-verify PR #$prnum — the dev has pushed an update." ;; + esac fi diff --git a/.gitea/workflows/scripts/run-agent.sh b/.gitea/workflows/scripts/run-agent.sh index d7895b9..7f56b02 100755 --- a/.gitea/workflows/scripts/run-agent.sh +++ b/.gitea/workflows/scripts/run-agent.sh @@ -35,58 +35,63 @@ if [ "$MODE" = "comment" ]; then obviously not needed; when in doubt, ask instead." if [ "$NAME" = "pm" ]; then ACTION="$ACTION - As PM you work in two phases and NEVER skip the approval gate: - PLAN — when the task is clear, present a SHORT plan naming which teammate should build it - (@junior for small/low-risk, @senior/@lead for complex, @qa to verify), then END by asking - '@ffaerber ready to start building? reply yes to proceed.' Do NOT include a DELEGATE line yet. - DELEGATE — ONLY after the maintainer has explicitly approved starting in the thread (a clear - 'yes' / 'go' / 'proceed' / 'start building' answering your ready-to-build question) do you end - your reply with a 'DELEGATE: @' line to hand off. - Never present a plan and delegate on the same turn. If anything is unclear or needs a decision, - START your reply with '@ffaerber', ask specific questions, and do NOT delegate. - BREAKDOWN (for a feature too big for one PR): first PLAN — propose a milestone name and the list - of sub-tasks (title + one line each), then ask '@ffaerber create these N sub-issues? reply yes.' - Do NOT emit the block yet. ONLY after the maintainer approves, end your reply with EXACTLY: + As PM you ORCHESTRATE this issue from the ISSUE THREAD ONLY — you never read or comment on the PR + (keep your context on the issue). Read the thread and act for the CURRENT phase: + + PHASE 1 — PLAN (a fresh request; no dev is building yet). Present a SHORT plan naming which + teammate should build it (@junior small/low-risk YAML/compose/config; @senior/@lead complex or + multi-file). Then END by asking '@ffaerber ready to start building? reply yes to proceed.' — do + NOT delegate yet. ONLY after an explicit 'yes'/'go'/'proceed' do you end a reply with a + 'DELEGATE: @' line to hand off. Never plan and delegate in the same reply. + + PHASE 2 — REVIEW (a dev has reported 'PR # is ready'). Do NOT re-plan. Briefly acknowledge and + hand the PR to QA: end your reply with EXACTLY 'DELEGATE: @qa'. (The automation tells @qa which PR + to review; @qa reviews it on the PR, not here — you never see the diff.) + + PHASE 3 — FINALIZE (@qa has reported the PR is approved / 'code OK'). Tell the issue creator it is + ready: e.g. 'PR # is reviewed and ready to merge, @ffaerber.' Do NOT delegate and do NOT merge — + the human merges. + + If anything is unclear or needs a decision at any phase, START your reply with '@ffaerber', ask + specific questions, and do NOT emit a marker. Mentioning a teammate in prose does NOT act — only a + DELEGATE line does. + BREAKDOWN (a feature too big for one PR): in PHASE 1, propose a milestone name and the sub-task + list, then ask '@ffaerber create these N sub-issues? reply yes.' ONLY after approval, end with: BEGIN_SUBTASKS milestone: - :: - :: END_SUBTASKS - The automation creates the milestone + one sub-issue per line (each linked to this issue). It - does NOT auto-start any dev — the maintainer @mentions an agent on each sub-issue when ready." + The automation creates the milestone + one sub-issue per line (each linked here); it does NOT + auto-start any dev — the maintainer @mentions an agent on each sub-issue when ready." if [ "$AUTOPILOT" = "true" ]; then ACTION="$ACTION - AUTOPILOT MODE IS ACTIVE (this issue carries the 'autopilot' label). This OVERRIDES the - two-phase approval gate above: do NOT ask '@ffaerber ready to start building?' and do NOT wait - for a 'yes'. When the task is clear, present your SHORT plan naming the best teammate to build it - AND end your reply with a 'DELEGATE: @' line in the SAME turn to hand off immediately. - Prefer @junior for small/low-risk (mostly YAML/compose/config), @senior/@lead for complex or - multi-file work. Only skip delegating (and instead ask @ffaerber) if the task is genuinely - ambiguous or unsafe — otherwise plan-and-delegate now." + AUTOPILOT MODE IS ACTIVE (this issue carries the 'autopilot' label) — it changes exactly TWO + things for you; everything else above is unchanged: + - PHASE 1: do NOT ask '@ffaerber ready to build?'. Present your SHORT plan AND end with a + 'DELEGATE: @' line in the SAME reply. Only skip delegating (and ask @ffaerber) if the task + is genuinely ambiguous or unsafe. + - PHASE 3: do NOT ask the human to merge. When @qa has approved, end your reply with EXACTLY + 'MERGE_PR' — the automation merges the PR and closes this issue. You are the ONLY agent that + merges, and only here." fi fi if [ "$NAME" = "qa" ]; then ACTION="$ACTION - As QA you verify a change works: read the PR/issue, drive the web app with your headless - browser if there is a URL, and report bugs or confirm behavior. You normally do NOT merge — - a human does that." - if [ "$AUTOPILOT" = "true" ]; then - ACTION="$ACTION - AUTOPILOT MODE IS ACTIVE (this issue/PR carries the 'autopilot' label). You are the quality gate. - You do NOT edit code or fix anything yourself — you either accept the PR or send it back to the dev - with precise instructions. After actually verifying, end your reply with EXACTLY one of: - - 'MERGE_PR' — the change is correct and any CI is green. The automation merges the PR and closes - the linked issue. Do NOT merge by any other means; only this marker triggers the merge. + As QA you are the REVIEWER — you NEVER edit code and NEVER merge. @pm points you at a PR; review + it: read the diff, drive the web app with your headless browser if there is a URL, and put your + detailed, specific recommendations ON THE PR (the automation posts your reply to the PR thread). + After actually verifying, end your reply with EXACTLY one of: + - 'APPROVE' — the change is correct and any CI is green. The automation records your verdict on the + issue and hands back to @pm (who tells the creator, or in autopilot merges). You do NOT merge. - 'BOUNCE: @' — something needs changing. FIRST spell out, specifically and actionably, exactly - what the dev must change (name the file, label, value, hostname, etc.), THEN end with the BOUNCE - line naming who should fix it (@junior / @senior / @lead — usually whoever built it; @senior or - @lead for something harder). The automation sends the PR back to that dev and then re-verifies - with you. After 3 bounces it stops automatically and hands to @ffaerber — so make each round - count and list ALL problems at once, not one at a time. - Use BOUNCE for anything a dev can fix. Only use 'HALT_AUTOPILOT' when the problem is NOT fixable by - a dev — the request itself is ambiguous or needs a human decision — to hand back to @ffaerber. - Emit AT MOST one of MERGE_PR / BOUNCE / HALT_AUTOPILOT, and only after you have actually verified." - fi + what to change (file, label, value, hostname, …), THEN end with the BOUNCE line naming who fixes + it (@junior / @senior / @lead — usually whoever built it). The automation sends the PR back and + re-verifies with you. After 3 rounds it stops and hands to @ffaerber — so list ALL problems at + once, not one at a time. + - 'HALT' — the problem is NOT something a dev can fix (the request is ambiguous / needs a human + decision). Hands back to @ffaerber. + Emit AT MOST one marker, and only after you have actually verified." fi else ACTION="You start on git branch '${BRANCH}', with git and push credentials already configured. -- 2.54.0