diff --git a/scripts/lib/labels.lib.sh b/scripts/lib/labels.lib.sh new file mode 100644 index 00000000..927d4036 --- /dev/null +++ b/scripts/lib/labels.lib.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# labels.lib.sh — Idempotent label creation for fullsend dispatch labels. +# +# Source from post-scripts: +# source "${SCRIPT_DIR}/lib/labels.lib.sh" + +# shellcheck shell=bash + +[[ -n "${LABELS_LIB_SH_LOADED:-}" ]] && return 0 +LABELS_LIB_SH_LOADED=1 + +# _label_defaults LABEL — print "description\tcolor" for known labels. +# Returns 1 for unknown labels (caller should handle). +_label_defaults() { + case "$1" in + ready-for-review) printf '%s\t%s' 'Fullsend: triggers review agent dispatch' '0E8A16' ;; + ready-to-code) printf '%s\t%s' 'Fullsend: triggers code agent dispatch' '0e8a16' ;; + ready-for-triage) printf '%s\t%s' 'Fullsend: awaiting triage agent' 'ededed' ;; + ready-for-merge) printf '%s\t%s' 'Fullsend: all reviewers approved' '0E8A16' ;; + requires-manual-review) printf '%s\t%s' 'Fullsend: review requires human judgment' 'FBCA04' ;; + rejected) printf '%s\t%s' 'Fullsend: approach rejected by review' 'B60205' ;; + needs-human) printf '%s\t%s' 'Fullsend: agent loop needs human input' 'D93F0B' ;; + pr-open) printf '%s\t%s' 'Fullsend: open PR addresses this issue' 'D4C5F9' ;; + needs-info) printf '%s\t%s' 'Fullsend: issue needs more information' 'd876e3' ;; + blocked) printf '%s\t%s' 'Fullsend: issue blocked on prerequisites' 'e11d48' ;; + duplicate) printf '%s\t%s' 'Fullsend: duplicate issue' 'cfd3d7' ;; + triaged) printf '%s\t%s' 'Fullsend: triaged, awaiting prioritization' 'c2e0c6' ;; + question) printf '%s\t%s' 'Fullsend: issue is a question' 'd876e3' ;; + bug) printf '%s\t%s' 'Fullsend: bug report' 'd73a4a' ;; + documentation) printf '%s\t%s' 'Fullsend: documentation improvement' '0075ca' ;; + feature) printf '%s\t%s' 'Fullsend: feature request' 'a2eeef' ;; + not-planned) printf '%s\t%s' 'Fullsend: will not be implemented' 'ffffff' ;; + *) return 1 ;; + esac +} + +# ensure_label REPO LABEL — create a label if it does not already exist. +# Uses defaults from _label_defaults when available. No-op when the label +# already exists (gh label create returns non-zero for duplicates). +# Always returns 0 so callers don't need error handling. +ensure_label() { + local repo="$1" label="$2" + local defaults desc color + local -a create_args=("$label" --repo "$repo") + + if defaults=$(_label_defaults "$label"); then + desc="${defaults%% *}" + color="${defaults##* }" + create_args+=(--description "$desc" --color "$color") + fi + + local err + if ! err=$(gh label create "${create_args[@]}" 2>&1); then + case "$err" in + *already\ exists*) ;; + *) echo "Warning: gh label create '${label}' failed: ${err}" >&2 ;; + esac + fi + return 0 +} diff --git a/scripts/post-code-test.sh b/scripts/post-code-test.sh index e1b7b99b..cd848341 100755 --- a/scripts/post-code-test.sh +++ b/scripts/post-code-test.sh @@ -1376,6 +1376,30 @@ run_branch_validation_test "no-agent-target-ignores-allowed-list" \ run_branch_validation_test "substring-not-accepted" \ "release" "main" "release-1,release-2" "reject:release" +# --------------------------------------------------------------------------- +# Verify the bundled script uses ensure_label from labels.lib.sh for the +# ready-for-review label, rather than inline create-on-missing fallback. +# --------------------------------------------------------------------------- + +# Source script must call ensure_label for ready-for-review +if grep -q 'ensure_label.*ready-for-review' "${POST_SCRIPT}"; then + echo "PASS: script-calls-ensure-label" +else + echo "FAIL: script-calls-ensure-label" + echo " ${POST_SCRIPT} does not call ensure_label for ready-for-review" + FAILURES=$((FAILURES + 1)) +fi + +# Bundled script must have labels.lib.sh inlined (ensure_label + _label_defaults) +BUNDLED_SCRIPT="${SCRIPT_DIR}/post-code.sh" +if grep -q '_label_defaults' "${BUNDLED_SCRIPT}" && grep -q 'ensure_label' "${BUNDLED_SCRIPT}"; then + echo "PASS: bundled-has-labels-lib" +else + echo "FAIL: bundled-has-labels-lib" + echo " ${BUNDLED_SCRIPT} missing labels.lib.sh functions" + FAILURES=$((FAILURES + 1)) +fi + # --- Summary --- echo "" diff --git a/scripts/post-code.sh b/scripts/post-code.sh index d44c4c41..8054f37c 100755 --- a/scripts/post-code.sh +++ b/scripts/post-code.sh @@ -37,7 +37,7 @@ # # Exit codes: # 0 — branch pushed and PR created, OR agent determined nothing to do -# 1 — validation failure or error (nothing pushed) +# 1 — validation failure, error, or post-push label application failure set -euo pipefail SCRIPT_DIR_POST="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -676,6 +676,68 @@ maybe_assign_pr() { } } # END bundled: lib/pr-assignee.lib.sh +# shellcheck source=lib/labels.lib.sh +# BEGIN bundled: lib/labels.lib.sh +# labels.lib.sh — Idempotent label creation for fullsend dispatch labels. +# +# Source from post-scripts: +# source "${SCRIPT_DIR}/lib/labels.lib.sh" + +# shellcheck shell=bash + +[[ -n "${LABELS_LIB_SH_LOADED:-}" ]] && return 0 +LABELS_LIB_SH_LOADED=1 + +# _label_defaults LABEL — print "description\tcolor" for known labels. +# Returns 1 for unknown labels (caller should handle). +_label_defaults() { + case "$1" in + ready-for-review) printf '%s\t%s' 'Fullsend: triggers review agent dispatch' '0E8A16' ;; + ready-to-code) printf '%s\t%s' 'Fullsend: triggers code agent dispatch' '0e8a16' ;; + ready-for-triage) printf '%s\t%s' 'Fullsend: awaiting triage agent' 'ededed' ;; + ready-for-merge) printf '%s\t%s' 'Fullsend: all reviewers approved' '0E8A16' ;; + requires-manual-review) printf '%s\t%s' 'Fullsend: review requires human judgment' 'FBCA04' ;; + rejected) printf '%s\t%s' 'Fullsend: approach rejected by review' 'B60205' ;; + needs-human) printf '%s\t%s' 'Fullsend: agent loop needs human input' 'D93F0B' ;; + pr-open) printf '%s\t%s' 'Fullsend: open PR addresses this issue' 'D4C5F9' ;; + needs-info) printf '%s\t%s' 'Fullsend: issue needs more information' 'd876e3' ;; + blocked) printf '%s\t%s' 'Fullsend: issue blocked on prerequisites' 'e11d48' ;; + duplicate) printf '%s\t%s' 'Fullsend: duplicate issue' 'cfd3d7' ;; + triaged) printf '%s\t%s' 'Fullsend: triaged, awaiting prioritization' 'c2e0c6' ;; + question) printf '%s\t%s' 'Fullsend: issue is a question' 'd876e3' ;; + bug) printf '%s\t%s' 'Fullsend: bug report' 'd73a4a' ;; + documentation) printf '%s\t%s' 'Fullsend: documentation improvement' '0075ca' ;; + feature) printf '%s\t%s' 'Fullsend: feature request' 'a2eeef' ;; + not-planned) printf '%s\t%s' 'Fullsend: will not be implemented' 'ffffff' ;; + *) return 1 ;; + esac +} + +# ensure_label REPO LABEL — create a label if it does not already exist. +# Uses defaults from _label_defaults when available. No-op when the label +# already exists (gh label create returns non-zero for duplicates). +# Always returns 0 so callers don't need error handling. +ensure_label() { + local repo="$1" label="$2" + local defaults desc color + local -a create_args=("$label" --repo "$repo") + + if defaults=$(_label_defaults "$label"); then + desc="${defaults%% *}" + color="${defaults##* }" + create_args+=(--description "$desc" --color "$color") + fi + + local err + if ! err=$(gh label create "${create_args[@]}" 2>&1); then + case "$err" in + *already\ exists*) ;; + *) echo "Warning: gh label create '${label}' failed: ${err}" >&2 ;; + esac + fi + return 0 +} +# END bundled: lib/labels.lib.sh # --------------------------------------------------------------------------- # Setup @@ -1332,9 +1394,15 @@ echo "pr_url=${PR_URL}" >> "${GITHUB_OUTPUT:-/dev/null}" # is used instead (label application requires repo write access). See # .github/scripts/check-e2e-authorization-test.sh for trusted-actor rules. PR_NUMBER_FROM_URL="${PR_URL##*/}" -gh issue edit "${PR_NUMBER_FROM_URL}" \ +ensure_label "${REPO_FULL_NAME}" "ready-for-review" +label_err="" +if label_err=$(gh issue edit "${PR_NUMBER_FROM_URL}" \ --repo "${REPO_FULL_NAME}" \ - --add-label "ready-for-review" 2>/dev/null || \ - gha_echo warning "Failed to apply ready-for-review label to PR #${PR_NUMBER_FROM_URL}" + --add-label "ready-for-review" 2>&1); then + echo "Applied ready-for-review label to PR #${PR_NUMBER_FROM_URL}" +else + gha_echo error "Failed to apply ready-for-review label to PR #${PR_NUMBER_FROM_URL} — review agent will NOT be dispatched: ${label_err}" + exit 1 +fi maybe_assign_pr "${PR_NUMBER_FROM_URL}" diff --git a/scripts/post-code.src.sh b/scripts/post-code.src.sh index 3a9c7292..2943b59e 100755 --- a/scripts/post-code.src.sh +++ b/scripts/post-code.src.sh @@ -36,7 +36,7 @@ # # Exit codes: # 0 — branch pushed and PR created, OR agent determined nothing to do -# 1 — validation failure or error (nothing pushed) +# 1 — validation failure, error, or post-push label application failure set -euo pipefail SCRIPT_DIR_POST="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -46,6 +46,8 @@ source "${SCRIPT_DIR_POST}/lib/post-failure-report.lib.sh" source "${SCRIPT_DIR_POST}/lib/gitleaks-install.lib.sh" # shellcheck source=lib/pr-assignee.lib.sh source "${SCRIPT_DIR_POST}/lib/pr-assignee.lib.sh" +# shellcheck source=lib/labels.lib.sh +source "${SCRIPT_DIR_POST}/lib/labels.lib.sh" # --------------------------------------------------------------------------- # Setup @@ -702,9 +704,15 @@ echo "pr_url=${PR_URL}" >> "${GITHUB_OUTPUT:-/dev/null}" # is used instead (label application requires repo write access). See # .github/scripts/check-e2e-authorization-test.sh for trusted-actor rules. PR_NUMBER_FROM_URL="${PR_URL##*/}" -gh issue edit "${PR_NUMBER_FROM_URL}" \ +ensure_label "${REPO_FULL_NAME}" "ready-for-review" +label_err="" +if label_err=$(gh issue edit "${PR_NUMBER_FROM_URL}" \ --repo "${REPO_FULL_NAME}" \ - --add-label "ready-for-review" 2>/dev/null || \ - gha_echo warning "Failed to apply ready-for-review label to PR #${PR_NUMBER_FROM_URL}" + --add-label "ready-for-review" 2>&1); then + echo "Applied ready-for-review label to PR #${PR_NUMBER_FROM_URL}" +else + gha_echo error "Failed to apply ready-for-review label to PR #${PR_NUMBER_FROM_URL} — review agent will NOT be dispatched: ${label_err}" + exit 1 +fi maybe_assign_pr "${PR_NUMBER_FROM_URL}" diff --git a/scripts/post-triage-test.sh b/scripts/post-triage-test.sh index 67a966cd..f3c9a9ca 100755 --- a/scripts/post-triage-test.sh +++ b/scripts/post-triage-test.sh @@ -326,7 +326,7 @@ run_test "in-progress-multiple-prs-second-linked" \ run_test "in-progress-creates-pr-open-label" \ '{"action":"in-progress","reasoning":"PR #50 fixes the reported bug","pull_requests":[{"url":"https://github.com/test-org/test-repo/pull/50"}],"comment":"An open PR is already addressing this issue."}' \ - "gh label create pr-open --repo test-org/test-repo --description An open PR already addresses this issue --color D4C5F9 --force" + "gh label create pr-open --repo test-org/test-repo --description Fullsend: open PR addresses this issue --color D4C5F9" run_test "in-progress-missing-comment-fails" \ '{"action":"in-progress","reasoning":"PR #50 fixes the reported bug","pull_requests":[{"url":"https://github.com/test-org/test-repo/pull/50"}]}' \ diff --git a/scripts/post-triage.sh b/scripts/post-triage.sh index 5455a453..a2970219 100755 --- a/scripts/post-triage.sh +++ b/scripts/post-triage.sh @@ -19,6 +19,10 @@ set -euo pipefail +SCRIPT_DIR_TRIAGE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/labels.lib.sh +source "${SCRIPT_DIR_TRIAGE}/lib/labels.lib.sh" + # Find the triage result JSON — prefer the validated iteration when set. # Trust boundary: FULLSEND_VALIDATED_ITERATION_DIR is set by the fullsend CLI # on the runner — not by the sandbox or the agent. No containment check @@ -74,6 +78,7 @@ echo "Issue: #${ISSUE_NUMBER}" # add_label uses the labels API to avoid firing issues.edited. add_label() { + ensure_label "${REPO}" "$1" local endpoint="repos/${REPO}/issues/${ISSUE_NUMBER}/labels" local err_output if ! err_output=$(gh api "${endpoint}" -f "labels[]=$1" --silent 2>&1); then @@ -306,9 +311,6 @@ ${FAILED_CREATES}" remove_label "blocked" remove_label "ready-to-code" remove_label "needs-info" - gh label create "pr-open" --repo "${REPO}" \ - --description "An open PR already addresses this issue" --color "D4C5F9" \ - --force 2>/dev/null || true add_label "pr-open" ;;