Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .agents/scripts/commands/pulse.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ Then skip to the next PR. The next pulse cycle will retry the permission check

**For maintainer PRs (admin/maintain/write permission):**

- **Green CI + no blocking reviews** → merge: `gh pr merge <number> --repo <slug> --squash`. If the PR resolves an issue, the issue should be closed with a comment linking to the merged PR.
- **Green CI + at least one review posted + no blocking reviews** → merge: `gh pr merge <number> --repo <slug> --squash`. If the PR resolves an issue, the issue should be closed with a comment linking to the merged PR.
- **CRITICAL (t2839):** Always verify the formal review count first: `gh pr view <number> --repo <slug> --json reviews --jq '.reviews | length'`. If count > 0, the review gate passes. If `review-bot-gate-helper.sh check <number> <slug>` is available, use it as an additional bot-activity signal — `PASS` from the bot gate is sufficient on its own. However, `WAITING` only means "no known bot activity" — it does NOT mean zero reviews. When `WAITING` is returned, check the review count explicitly (the `gh pr view` command above); if count > 0, proceed to merge. `SKIP` means the PR has a `skip-review-gate` label — it bypasses the bot gate only, NOT the review count requirement. Skip the PR only when both the bot gate is not `PASS` AND the formal review count is 0.
- **Green CI + zero reviews** → skip this cycle. Zero reviews means "not yet reviewed", NOT "clean to merge". Review bots typically post within 2-5 minutes. The next pulse will pick it up once a review exists.
- **Failing CI or changes requested** → dispatch a worker to fix it (counts against worker slots)

**For all PRs (regardless of author):**
Expand Down
53 changes: 51 additions & 2 deletions .agents/scripts/supervisor-archived/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -342,15 +342,64 @@ cmd_pr_lifecycle() {
pr_number_fastpath="${parsed_fastpath##*|}"

if [[ -n "$pr_number_fastpath" && -n "$repo_slug_fastpath" ]]; then
# t2839: Check that at least one review exists before fast-path merge.
# Zero reviews means "not yet reviewed", not "clean to merge".
# Always count formal reviews (human or bot) via gh API as the
# authoritative source. The bot gate is an additional signal only.
local review_gate_result="UNKNOWN"
local review_count_fastpath=""
review_count_fastpath=$(gh pr view "$pr_number_fastpath" --repo "$repo_slug_fastpath" \
--json reviews --jq '.reviews | length' 2>>"${SUPERVISOR_LOG:-/dev/null}" || echo "")

# Optional bot-signal gate (only PASS is sufficient on its own)
local bot_gate_result="WAITING"
local review_bot_gate_script
review_bot_gate_script="$(dirname "$(dirname "${BASH_SOURCE[0]}")")/review-bot-gate-helper.sh"
if [[ -x "$review_bot_gate_script" ]]; then
bot_gate_result=$("$review_bot_gate_script" check "$pr_number_fastpath" "$repo_slug_fastpath" 2>>"${SUPERVISOR_LOG:-/dev/null}") || bot_gate_result="WAITING"
fi

# Determine review gate result:
# - Bot gate PASS = bot confirmed reviews exist → sufficient
# - Bot gate SKIP = label-driven bypass of bot check, NOT proof of reviews
# SKIP only skips the bot gate; the review count check still applies
# - review_count > 0 = at least one formal review exists → sufficient
# - Otherwise → WAITING (no reviews yet)
if [[ "$bot_gate_result" == "PASS" ]]; then
review_gate_result="PASS"
elif [[ "$review_count_fastpath" =~ ^[0-9]+$ && "$review_count_fastpath" -gt 0 ]]; then
review_gate_result="PASS"
else
review_gate_result="WAITING"
fi

if [[ "$review_gate_result" == "WAITING" ]]; then
log_info "Fast-path blocked: no reviews posted yet for $task_id — waiting for review before merge (t2839)"
# Stay in pr_review state; next pulse will re-check
local stage_end
stage_end=$(date +%s)
stage_timings="${stage_timings}pr_review:$((stage_end - stage_start))s(no_reviews),"
record_lifecycle_timing "$task_id" "$stage_timings" 2>>"${SUPERVISOR_LOG:-/dev/null}" || true
return 0
fi

local threads_json_fastpath
threads_json_fastpath=$(check_review_threads "$repo_slug_fastpath" "$pr_number_fastpath" 2>/dev/null || echo "[]")
local thread_count_fastpath
thread_count_fastpath=$(echo "$threads_json_fastpath" | jq 'length' 2>/dev/null || echo "0")

if [[ "$thread_count_fastpath" -eq 0 ]]; then
log_info "Fast-path: CI green + zero review threads - skipping review_triage, going directly to merge${merge_note}"
log_info "Fast-path: CI green + reviews posted + zero unresolved threads - skipping review_triage, going directly to merge${merge_note}"
if [[ "$dry_run" == "false" ]]; then
db "$SUPERVISOR_DB" "UPDATE tasks SET triage_result = '{\"action\":\"merge\",\"threads\":0,\"fast_path\":true,\"sonarcloud_unstable\":$(if [[ "$pr_status" == "unstable_sonarcloud" ]]; then echo "true"; else echo "false"; fi)}' WHERE id = '$escaped_id';"
# Use parameterized JSON construction to prevent SQL injection (Gemini review feedback)
local sonarcloud_flag="false"
if [[ "$pr_status" == "unstable_sonarcloud" ]]; then
sonarcloud_flag="true"
fi
local triage_json
triage_json=$(jq -n --arg gate "$review_gate_result" --argjson sc "$sonarcloud_flag" \
'{"action":"merge","threads":0,"fast_path":true,"review_gate":$gate,"sonarcloud_unstable":$sc}')
db "$SUPERVISOR_DB" "UPDATE tasks SET triage_result = '$(echo "$triage_json" | sed "s/'/''/g")' WHERE id = '$escaped_id';"
cmd_transition "$task_id" "merging" 2>>"$SUPERVISOR_LOG" || true
fi
tstatus="merging"
Expand Down