agents: PM-orchestrated flow (@pm hub, @qa reviews, @pm/human merges) #69
@@ -32,6 +32,20 @@ hdr=(-H "Authorization: token $TOK" -H "Content-Type: application/json")
|
|||||||
MARK=$'\n\n<!-- 🤖 agent reply — do not trigger -->'
|
MARK=$'\n\n<!-- 🤖 agent reply — do not trigger -->'
|
||||||
post() { curl -sS -w 'comment -> HTTP %{http_code}\n' -X POST "${hdr[@]}" \
|
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}')"; }
|
"$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-<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
|
# 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.
|
# endpoint is by ID, not name). Arg $1 = issue number. Used as the autopilot kill switch.
|
||||||
del_autopilot_label() {
|
del_autopilot_label() {
|
||||||
@@ -46,7 +60,7 @@ del_autopilot_label() {
|
|||||||
fi
|
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
|
# BEGIN_SUBTASKS..END_SUBTASKS and BEGIN_PR_DESCRIPTION..END_PR_DESCRIPTION blocks (the PR
|
||||||
# description is published separately).
|
# description is published separately).
|
||||||
reply=$(awk '
|
reply=$(awk '
|
||||||
@@ -55,7 +69,8 @@ reply=$(awk '
|
|||||||
/^[[:space:]]*DELEGATE:[[:space:]]*@/{next}
|
/^[[:space:]]*DELEGATE:[[:space:]]*@/{next}
|
||||||
/^[[:space:]]*CLOSE_ISSUE[[:space:]]*$/{next}
|
/^[[:space:]]*CLOSE_ISSUE[[:space:]]*$/{next}
|
||||||
/^[[:space:]]*MERGE_PR[[: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}
|
/^[[:space:]]*BOUNCE:[[:space:]]*@/{next}
|
||||||
s{ if(/^[[:space:]]*END_SUBTASKS/){s=0}; next }
|
s{ if(/^[[:space:]]*END_SUBTASKS/){s=0}; next }
|
||||||
p{ if(/^[[:space:]]*END_PR_DESCRIPTION/){p=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
|
if [ "$MODE" != "pr" ]; then
|
||||||
git checkout -- . 2>/dev/null || true
|
git checkout -- . 2>/dev/null || true
|
||||||
git clean -fd 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:]')
|
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.
|
# Visible comment: the reply text, or a sensible line if the agent only emitted a marker.
|
||||||
msg="$reply"
|
msg="$reply"
|
||||||
@@ -127,92 +175,49 @@ if [ "$MODE" != "pr" ]; then
|
|||||||
fi
|
fi
|
||||||
post "$(printf '%s%s' "$msg" "$subtext")"
|
post "$(printf '%s%s' "$msg" "$subtext")"
|
||||||
|
|
||||||
# --- AUTOPILOT: @qa's narrow, label-gated merge / halt authority ---
|
# --- @pm autopilot merge: @pm is the ONLY agent that merges, and ONLY under the autopilot label ---
|
||||||
# Only @qa, only when 'autopilot' is set, and only on a PR thread. The MERGE_PR / HALT_AUTOPILOT
|
# (@qa never merges — it approves and hands back here.) Merge with the PAT (TTOK), not the built-in
|
||||||
# markers come from the QA prompt. Merge + label ops use TOKEN_QA (the QA user's PAT, which the
|
# token, so the push to main fires the deploy. TOKEN_PM must carry write:repository.
|
||||||
# maintainer must grant write+merge scope). ISSNUM is the origin issue (resolved from the branch).
|
if [ "$NAME" = "pm" ] && [ "$AUTOPILOT" = "true" ] && grep -qiE '^[[:space:]]*MERGE_PR[[:space:]]*$' /tmp/agent_out.md; then
|
||||||
if [ "$NAME" = "qa" ] && [ "$AUTOPILOT" = "true" ]; then
|
PRN=$(resolve_pr)
|
||||||
if grep -qiE '^[[:space:]]*MERGE_PR[[:space:]]*$' /tmp/agent_out.md; then
|
if [ -z "$PRN" ]; then
|
||||||
if [ -z "$IS_PR" ]; then
|
echo "MERGE_PR but no open PR found for issue #$ISSN"
|
||||||
echo "MERGE_PR marker but this run is not on a PR thread — skipping merge"
|
|
||||||
else
|
else
|
||||||
echo "@qa autopilot: merging PR #$NUM (origin issue #${ISSNUM:-$NUM})"
|
echo "@pm autopilot: merging PR #$PRN (issue #$ISSN)"
|
||||||
# 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 \
|
mc=$(curl -sS -o /tmp/merge_resp.txt -w '%{http_code}' -X POST \
|
||||||
-H "Authorization: token $mtok" -H "Content-Type: application/json" \
|
-H "Authorization: token $TTOK" -H "Content-Type: application/json" \
|
||||||
"$API/pulls/$NUM/merge" -d '{"Do":"merge"}')
|
"$API/pulls/$PRN/merge" -d '{"Do":"merge"}')
|
||||||
echo "merge -> HTTP $mc"; cat /tmp/merge_resp.txt 2>/dev/null || true
|
echo "merge -> HTTP $mc"; cat /tmp/merge_resp.txt 2>/dev/null || true
|
||||||
case "$mc" in
|
case "$mc" in
|
||||||
200|201|204)
|
200|201|204)
|
||||||
echo "closing origin issue #${ISSNUM:-$NUM}"
|
curl -sS -X PATCH "${hdr[@]}" "$API/issues/$ISSN" -d '{"state":"closed"}' -w '\nclose -> HTTP %{http_code}\n' || true
|
||||||
curl -sS -X PATCH "${hdr[@]}" "$API/issues/${ISSNUM:-$NUM}" \
|
post_to "$ISSN" "✅ Merged PR #$PRN (autopilot) and closed this issue." ;;
|
||||||
-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
|
del_autopilot_label "$ISSN"
|
||||||
# silently proceed: drop the label so it reverts to human control and report.
|
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." ;;
|
||||||
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
|
esac
|
||||||
fi
|
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).')"
|
|
||||||
fi
|
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Auto-delegate: if the plan names a teammate, trigger them via TOK (a PAT, so it
|
# --- @pm delegation: hand the build to a dev, or hand the finished PR to @qa for review ---
|
||||||
# fires a new workflow run — the built-in token cannot). Never targets @pm or self, so the
|
# Only an explicit 'DELEGATE: @<agent>' line acts (never a prose mention). Fires via the PAT (TTOK)
|
||||||
# chain always terminates at a dev. The '🤖' guard on the trigger stops status-comment loops.
|
# so a new run starts; the built-in token cannot. Everything posts on the ISSUE — @pm never touches
|
||||||
if [ -n "$TTOK" ]; then
|
# the PR. Chain terminates: normal → @pm tells the creator (no marker); autopilot → @pm merges above.
|
||||||
# Only delegate on an explicit "DELEGATE: @<agent>" 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
|
if [ -n "$target" ] && [ "$target" != "$NAME" ]; then
|
||||||
echo "auto-delegating to @$target"
|
if [ "$target" = "qa" ]; then
|
||||||
curl -sS -X POST -H "Authorization: token $TTOK" -H "Content-Type: application/json" \
|
PRN=$(resolve_pr)
|
||||||
"$API/issues/$NUM/comments" \
|
if [ -n "$PRN" ]; then
|
||||||
-d "$(jq -nc --arg b "@$target please proceed with issue #$NUM per the plan above (delegated by $NAME)." '{body:$b}')" \
|
trig "$ISSN" "@qa please review PR #$PRN for issue #$ISSN — put your recommendations on the PR, or approve."
|
||||||
-w '\ndelegate -> HTTP %{http_code}\n' || true
|
else
|
||||||
|
echo "DELEGATE:@qa but no open PR yet for issue #$ISSN — not firing"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
trig "$ISSN" "@$target please proceed with issue #$ISSN per the plan above (delegated by $NAME)."
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "no DELEGATE marker — not delegating (agent is asking or finished)"
|
echo "no DELEGATE marker — not delegating (agent is asking or finished)"
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -288,32 +293,15 @@ prpost() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if [ "$NEW" = "true" ]; then
|
if [ "$NEW" = "true" ]; then
|
||||||
prpost "$prnum" "$(printf '✅ PR ready for review — @ffaerber please review & merge:\n- %s%s' "$url" "$activity")"
|
# First PR for this issue: record it on the PR thread, then notify @pm on the ISSUE. @pm never
|
||||||
# AUTOPILOT: hand the fresh PR to @qa automatically (via TOK, so it fires a new run).
|
# reads the PR, so the issue gets only this one-line ping — @pm then routes it to @qa for review.
|
||||||
# @qa then verifies and — if green — merges + closes via its MERGE_PR marker. The comment lands
|
prpost "$prnum" "$(printf 'Opened PR #%s for review.%s' "$prnum" "$activity")"
|
||||||
# on the PR thread ($prnum) so the next run resolves the origin issue's label from the branch
|
trig "$ISSN" "@pm — PR #$prnum is ready for review (issue #$ISSN)."
|
||||||
# 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
|
|
||||||
else
|
else
|
||||||
# Resume: just link the PR — its body and the diff already carry the description, so we don't
|
# A fix (usually after a @qa bounce): update the PR and hand straight back to @qa to re-verify,
|
||||||
# repeat the full write-up in the comment (the reasoning trail below shows what this run did).
|
# 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 the PR:\n- %s%s' "$url" "$activity")"
|
prpost "$prnum" "$(printf 'Pushed an update to PR #%s.%s' "$prnum" "$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
|
case "$NAME" in
|
||||||
junior|senior|lead)
|
junior|senior|lead) trig "$prnum" "@qa please re-verify PR #$prnum — the dev has pushed an update." ;;
|
||||||
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
|
esac
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|||||||
@@ -35,58 +35,63 @@ if [ "$MODE" = "comment" ]; then
|
|||||||
obviously not needed; when in doubt, ask instead."
|
obviously not needed; when in doubt, ask instead."
|
||||||
if [ "$NAME" = "pm" ]; then
|
if [ "$NAME" = "pm" ]; then
|
||||||
ACTION="$ACTION
|
ACTION="$ACTION
|
||||||
As PM you work in two phases and NEVER skip the approval gate:
|
As PM you ORCHESTRATE this issue from the ISSUE THREAD ONLY — you never read or comment on the PR
|
||||||
PLAN — when the task is clear, present a SHORT plan naming which teammate should build it
|
(keep your context on the issue). Read the thread and act for the CURRENT phase:
|
||||||
(@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.
|
PHASE 1 — PLAN (a fresh request; no dev is building yet). Present a SHORT plan naming which
|
||||||
DELEGATE — ONLY after the maintainer has explicitly approved starting in the thread (a clear
|
teammate should build it (@junior small/low-risk YAML/compose/config; @senior/@lead complex or
|
||||||
'yes' / 'go' / 'proceed' / 'start building' answering your ready-to-build question) do you end
|
multi-file). Then END by asking '@ffaerber ready to start building? reply yes to proceed.' — do
|
||||||
your reply with a 'DELEGATE: @<agent>' line to hand off.
|
NOT delegate yet. ONLY after an explicit 'yes'/'go'/'proceed' do you end a reply with a
|
||||||
Never present a plan and delegate on the same turn. If anything is unclear or needs a decision,
|
'DELEGATE: @<dev>' line to hand off. Never plan and delegate in the same reply.
|
||||||
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
|
PHASE 2 — REVIEW (a dev has reported 'PR #<n> is ready'). Do NOT re-plan. Briefly acknowledge and
|
||||||
of sub-tasks (title + one line each), then ask '@ffaerber create these N sub-issues? reply yes.'
|
hand the PR to QA: end your reply with EXACTLY 'DELEGATE: @qa'. (The automation tells @qa which PR
|
||||||
Do NOT emit the block yet. ONLY after the maintainer approves, end your reply with EXACTLY:
|
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 #<n> 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
|
BEGIN_SUBTASKS
|
||||||
milestone: <feature name>
|
milestone: <feature name>
|
||||||
- <task title> :: <one-line description>
|
- <task title> :: <one-line description>
|
||||||
- <task title> :: <one-line description>
|
- <task title> :: <one-line description>
|
||||||
END_SUBTASKS
|
END_SUBTASKS
|
||||||
The automation creates the milestone + one sub-issue per line (each linked to this issue). It
|
The automation creates the milestone + one sub-issue per line (each linked here); it does NOT
|
||||||
does NOT auto-start any dev — the maintainer @mentions an agent on each sub-issue when ready."
|
auto-start any dev — the maintainer @mentions an agent on each sub-issue when ready."
|
||||||
if [ "$AUTOPILOT" = "true" ]; then
|
if [ "$AUTOPILOT" = "true" ]; then
|
||||||
ACTION="$ACTION
|
ACTION="$ACTION
|
||||||
AUTOPILOT MODE IS ACTIVE (this issue carries the 'autopilot' label). This OVERRIDES the
|
AUTOPILOT MODE IS ACTIVE (this issue carries the 'autopilot' label) — it changes exactly TWO
|
||||||
two-phase approval gate above: do NOT ask '@ffaerber ready to start building?' and do NOT wait
|
things for you; everything else above is unchanged:
|
||||||
for a 'yes'. When the task is clear, present your SHORT plan naming the best teammate to build it
|
- PHASE 1: do NOT ask '@ffaerber ready to build?'. Present your SHORT plan AND end with a
|
||||||
AND end your reply with a 'DELEGATE: @<agent>' line in the SAME turn to hand off immediately.
|
'DELEGATE: @<dev>' line in the SAME reply. Only skip delegating (and ask @ffaerber) if the task
|
||||||
Prefer @junior for small/low-risk (mostly YAML/compose/config), @senior/@lead for complex or
|
is genuinely ambiguous or unsafe.
|
||||||
multi-file work. Only skip delegating (and instead ask @ffaerber) if the task is genuinely
|
- PHASE 3: do NOT ask the human to merge. When @qa has approved, end your reply with EXACTLY
|
||||||
ambiguous or unsafe — otherwise plan-and-delegate now."
|
'MERGE_PR' — the automation merges the PR and closes this issue. You are the ONLY agent that
|
||||||
|
merges, and only here."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ "$NAME" = "qa" ]; then
|
if [ "$NAME" = "qa" ]; then
|
||||||
ACTION="$ACTION
|
ACTION="$ACTION
|
||||||
As QA you verify a change works: read the PR/issue, drive the web app with your headless
|
As QA you are the REVIEWER — you NEVER edit code and NEVER merge. @pm points you at a PR; review
|
||||||
browser if there is a URL, and report bugs or confirm behavior. You normally do NOT merge —
|
it: read the diff, drive the web app with your headless browser if there is a URL, and put your
|
||||||
a human does that."
|
detailed, specific recommendations ON THE PR (the automation posts your reply to the PR thread).
|
||||||
if [ "$AUTOPILOT" = "true" ]; then
|
After actually verifying, end your reply with EXACTLY one of:
|
||||||
ACTION="$ACTION
|
- 'APPROVE' — the change is correct and any CI is green. The automation records your verdict on the
|
||||||
AUTOPILOT MODE IS ACTIVE (this issue/PR carries the 'autopilot' label). You are the quality gate.
|
issue and hands back to @pm (who tells the creator, or in autopilot merges). You do NOT merge.
|
||||||
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.
|
|
||||||
- 'BOUNCE: @<dev>' — something needs changing. FIRST spell out, specifically and actionably, exactly
|
- 'BOUNCE: @<dev>' — 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
|
what to change (file, label, value, hostname, …), THEN end with the BOUNCE line naming who fixes
|
||||||
line naming who should fix it (@junior / @senior / @lead — usually whoever built it; @senior or
|
it (@junior / @senior / @lead — usually whoever built it). The automation sends the PR back and
|
||||||
@lead for something harder). The automation sends the PR back to that dev and then re-verifies
|
re-verifies with you. After 3 rounds it stops and hands to @ffaerber — so list ALL problems at
|
||||||
with you. After 3 bounces it stops automatically and hands to @ffaerber — so make each round
|
once, not one at a time.
|
||||||
count and 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
|
||||||
Use BOUNCE for anything a dev can fix. Only use 'HALT_AUTOPILOT' when the problem is NOT fixable by
|
decision). Hands back to @ffaerber.
|
||||||
a dev — the request itself is ambiguous or needs a human decision — to hand back to @ffaerber.
|
Emit AT MOST one marker, and only after you have actually verified."
|
||||||
Emit AT MOST one of MERGE_PR / BOUNCE / HALT_AUTOPILOT, and only after you have actually verified."
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
ACTION="You start on git branch '${BRANCH}', with git and push credentials already configured.
|
ACTION="You start on git branch '${BRANCH}', with git and push credentials already configured.
|
||||||
|
|||||||
Reference in New Issue
Block a user