Skip to content
Merged
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
62 changes: 43 additions & 19 deletions .agents/scripts/supervisor/issue-sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ ensure_status_labels() {
gh label create "status:verify-failed" --repo "$repo_slug" --color "E4E669" --description "Task verification failed" --force 2>/dev/null || true
gh label create "status:needs-testing" --repo "$repo_slug" --color "FBCA04" --description "Code merged, needs manual or integration testing" --force 2>/dev/null || true
gh label create "status:done" --repo "$repo_slug" --color "6F42C1" --description "Task is complete" --force 2>/dev/null || true
gh label create "needs-review" --repo "$repo_slug" --color "E99695" --description "Flagged for human review by AI supervisor" --force 2>/dev/null || true
return 0
}

Expand Down Expand Up @@ -328,8 +329,9 @@ state_to_status_label() {
merged | deploying) echo "status:in-review" ;;
blocked) echo "status:blocked" ;;
verify_failed) echo "status:verify-failed" ;;
# Terminal states: verified/deployed close the issue, cancelled closes as not-planned
# These return empty — the caller handles close logic separately
# Terminal states: verified/deployed close the issue (only with merged PR evidence),
# cancelled closes as not-planned, failed flags for human review (never auto-closes).
# These return empty — the caller handles close/flag logic separately.
verified | deployed | cancelled | failed) echo "" ;;
*) echo "" ;;
esac
Expand Down Expand Up @@ -410,26 +412,39 @@ sync_issue_status_label() {
close_comment="Task $task_id reached state: $new_state (from $old_state)"
local pr_url=""
pr_url=$(db "$SUPERVISOR_DB" "SELECT pr_url FROM tasks WHERE id='$(sql_escape "$task_id")';" 2>/dev/null || echo "")
if [[ -n "$pr_url" && "$pr_url" != "null" ]]; then
local has_merged_pr="false"
if [[ -n "$pr_url" && "$pr_url" != "null" && "$pr_url" != "no_pr" && "$pr_url" != "task_only" && "$pr_url" != "task_obsolete" ]]; then
local pr_number=""
pr_number=$(echo "$pr_url" | grep -oE '[0-9]+$' || echo "")
if [[ -n "$pr_number" ]]; then
local pr_state=""
local pr_state="" pr_state_raw=""
pr_state=$(gh pr view "$pr_number" --repo "$repo_slug" --json state,mergedAt,changedFiles \
--jq '"state:\(.state) merged:\(.mergedAt // "n/a") files:\(.changedFiles)"' 2>/dev/null || echo "")
--jq '"state:\(.state) merged:\(.mergedAt // "n/a") files:\(.changedFiles)"' || echo "")
pr_state_raw=$(echo "$pr_state" | sed -n 's/^state:\([A-Z]*\).*/\1/p')
close_comment="Verified: PR #$pr_number ($pr_state). Task $task_id: $old_state -> $new_state"
# Only count as merged if PR state field is exactly MERGED
if [[ "$pr_state_raw" == "MERGED" ]]; then
has_merged_pr="true"
fi
fi
fi
if [[ -z "$pr_url" || "$pr_url" == "null" ]]; then
close_comment="Task $task_id reached state: $new_state (from $old_state). No PR on record — verify deliverables manually."
if [[ "$has_merged_pr" == "true" ]]; then
# Close the issue with proof-log comment — PR evidence confirmed
gh issue close "$issue_number" --repo "$repo_slug" \
--comment "$close_comment" 2>/dev/null || true
# Add status:done and remove all other status labels
gh issue edit "$issue_number" --repo "$repo_slug" \
--add-label "status:done" "${remove_args[@]}" 2>/dev/null || true
log_verbose "sync_issue_status_label: closed #$issue_number ($task_id -> $new_state) proof: ${pr_url:-none}"
else
# No merged PR evidence — do NOT auto-close. Flag for human review.
local review_comment="Task $task_id reached state: $new_state (from $old_state). No merged PR on record — flagged for human review instead of auto-closing."
gh issue comment "$issue_number" --repo "$repo_slug" \
--body "$review_comment" 2>/dev/null || true
gh issue edit "$issue_number" --repo "$repo_slug" \
--add-label "needs-review" "${remove_args[@]}" 2>/dev/null || true
log_verbose "sync_issue_status_label: flagged #$issue_number for review ($task_id -> $new_state, no merged PR)"
fi
# Close the issue with proof-log comment
gh issue close "$issue_number" --repo "$repo_slug" \
--comment "$close_comment" 2>/dev/null || true
# Add status:done and remove all other status labels
gh issue edit "$issue_number" --repo "$repo_slug" \
--add-label "status:done" "${remove_args[@]}" 2>/dev/null || true
log_verbose "sync_issue_status_label: closed #$issue_number ($task_id -> $new_state) proof: ${pr_url:-none}"
return 0
;;
cancelled)
Expand Down Expand Up @@ -457,12 +472,21 @@ sync_issue_status_label() {
if [[ -n "$fail_error" && "$fail_error" != "null" ]]; then
fail_comment="Task $task_id failed (was: $old_state). Error: $fail_error"
fi
# Close with failure comment but don't add status:done
gh issue close "$issue_number" --repo "$repo_slug" \
--comment "$fail_comment" 2>/dev/null || true
# DO NOT auto-close failed tasks — they need human review.
# Post failure comment and add needs-review label, keep issue OPEN.
gh issue comment "$issue_number" --repo "$repo_slug" \
--body "$fail_comment" 2>/dev/null || true
gh issue edit "$issue_number" --repo "$repo_slug" \
"${remove_args[@]}" 2>/dev/null || true
log_verbose "sync_issue_status_label: closed #$issue_number as failed ($task_id)"
--add-label "needs-review" "${remove_args[@]}" 2>/dev/null || true
log_verbose "sync_issue_status_label: flagged #$issue_number for review ($task_id failed)"
# Reopen if the issue was previously closed (e.g. verified -> failed retry)
local fail_issue_state
fail_issue_state=$(gh issue view "$issue_number" --repo "$repo_slug" --json state --jq '.state' 2>/dev/null || echo "")
if [[ "$fail_issue_state" == "CLOSED" ]]; then
gh issue reopen "$issue_number" --repo "$repo_slug" \
--comment "Reopening: task $task_id failed and needs human review." 2>/dev/null || true
log_verbose "sync_issue_status_label: reopened #$issue_number ($task_id failed, was closed)"
fi
return 0
;;
blocked)
Expand Down