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
33 changes: 24 additions & 9 deletions .github/workflows/reusable-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ jobs:
TRIGGERING_LABEL: ${{ github.event.label.name }}
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
PR_BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }}
PR_USER_LOGIN: ${{ github.event.pull_request.user.login }}
ORG_NAME: ${{ github.repository_owner }}
run: |
set -euo pipefail
Expand Down Expand Up @@ -114,28 +115,35 @@ jobs:
case "${EVENT_NAME}" in
issue_comment)
case "${COMMAND}" in
/fs-triage)
/triage)
STAGE="triage"
;;
/fs-code)
/code)
if [[ "${ISSUE_HAS_PR}" == "false" ]]; then
STAGE="code"
fi
;;
/fs-review)
/review)
STAGE="review"
;;
/fs-fix)
/fix)
if [[ "${ISSUE_HAS_PR}" == "true" ]]; then
if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then
STAGE="fix"
TRIGGER_SOURCE="${COMMENT_USER_LOGIN}"
fi
fi
;;
/fs-retro)
/retro|/fullsend)
if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then
STAGE="retro"
if [[ "${COMMAND}" == "/fullsend" ]]; then
SECOND_WORD="$(printf '%s\n' "${COMMENT_BODY}" | head -1 | awk '{print $2}')"
if [[ "${SECOND_WORD}" == "retro" ]]; then
STAGE="retro"
fi
else
STAGE="retro"
fi
fi
;;
*)
Expand Down Expand Up @@ -178,8 +186,14 @@ jobs:
if [[ -n "${PR_HEAD_REPO}" && -n "${PR_BASE_REPO}" ]]; then
if [[ "${PR_HEAD_REPO}" == "${PR_BASE_REPO}" ]]; then
if ! has_label "fullsend-no-fix" "${PR_LABELS}"; then
STAGE="fix"
TRIGGER_SOURCE="${REVIEW_USER_LOGIN}"
# Human PRs require the fullsend-fix label to auto-trigger
# the fix agent. The /fs-fix slash command (line ~129)
# intentionally bypasses this gate — authorized users can
# always trigger fix manually regardless of labels.
if [[ "${PR_USER_LOGIN}" =~ \[bot\]$ ]] || has_label "fullsend-fix" "${PR_LABELS}"; then
Comment thread
ascerra marked this conversation as resolved.
STAGE="fix"
TRIGGER_SOURCE="${REVIEW_USER_LOGIN}"
fi
fi
fi
fi
Expand Down Expand Up @@ -242,7 +256,8 @@ jobs:
fi
STAGE_ROLE="$STAGE"
case "$STAGE" in
code|fix) STAGE_ROLE="coder" ;;
code) STAGE_ROLE="coder" ;;
retro|prioritize) STAGE_ROLE="fullsend" ;;
esac
ROLES=$(yq '.roles[]' .fullsend/config.yaml 2>/dev/null || echo "")
if [[ -n "$ROLES" ]] && ! echo "$ROLES" | grep -Fqx "$STAGE_ROLE"; then
Expand Down
18 changes: 15 additions & 3 deletions .github/workflows/reusable-fix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -238,20 +238,32 @@ jobs:
echo "Fix iteration: ${ITERATION} (${FIX_COMMITS} previous fix commits)" >&2
echo "iteration=${ITERATION}" >> "${GITHUB_OUTPUT}"

- name: Check fullsend-no-fix label
- name: Check fix eligibility
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
TRIGGER_SOURCE: ${{ inputs.trigger_source }}
PR_NUM: ${{ steps.context.outputs.pr_number }}
SOURCE_REPO: ${{ inputs.source_repo }}
run: |
if [[ "${TRIGGER_SOURCE}" =~ \[bot\]$ ]]; then
HAS_NO_FIX=$(gh pr view "${PR_NUM}" --repo "${SOURCE_REPO}" \
--json labels --jq '[.labels[].name] | any(. == "fullsend-no-fix")' 2>/dev/null || echo "false")
PR_INFO=$(gh pr view "${PR_NUM}" --repo "${SOURCE_REPO}" \
--json labels,author --jq '{labels: [.labels[].name], author: .author.login}' 2>/dev/null \
|| echo '{"labels":[],"author":""}')

HAS_NO_FIX=$(echo "${PR_INFO}" | jq -r '.labels | any(. == "fullsend-no-fix")')
if [[ "${HAS_NO_FIX}" == "true" ]]; then
echo "::warning::PR #${PR_NUM} has 'fullsend-no-fix' label — skipping bot-triggered fix"
exit 1
fi

PR_AUTHOR=$(echo "${PR_INFO}" | jq -r '.author')
if [[ ! "${PR_AUTHOR}" =~ \[bot\]$ ]]; then
HAS_FIX_LABEL=$(echo "${PR_INFO}" | jq -r '.labels | any(. == "fullsend-fix")')
if [[ "${HAS_FIX_LABEL}" != "true" ]]; then
echo "::warning::Human-authored PR #${PR_NUM} without 'fullsend-fix' label — skipping bot-triggered fix"
exit 1
fi
fi
fi

- name: Checkout target repository at PR HEAD
Expand Down
37 changes: 26 additions & 11 deletions internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# lint-workflow-size: max-lines=380
# lint-workflow-size: max-lines=390
# Dispatcher workflow that routes events to agent workflows based on stage.
# Routing logic determines the stage from event context — the shim only
# forwards the raw event. Adding a new stage requires only a case branch
Expand Down Expand Up @@ -41,6 +41,7 @@ jobs:
TRIGGERING_LABEL: ${{ github.event.label.name }}
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
PR_BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }}
PR_USER_LOGIN: ${{ github.event.pull_request.user.login }}
ORG_NAME: ${{ github.repository_owner }}
run: |
set -euo pipefail
Expand Down Expand Up @@ -82,31 +83,38 @@ jobs:
case "${EVENT_NAME}" in
issue_comment)
case "${COMMAND}" in
/fs-triage)
/triage)
STAGE="triage"
;;
/fs-code)
/code)
if [[ "${ISSUE_HAS_PR}" == "false" ]]; then
STAGE="code"
fi
;;
/fs-review)
/review)
STAGE="review"
;;
/fs-fix)
/fix)
if [[ "${ISSUE_HAS_PR}" == "true" ]]; then
if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then
STAGE="fix"
TRIGGER_SOURCE="${COMMENT_USER_LOGIN}"
fi
fi
;;
/fs-retro)
/retro|/fullsend)
if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then
STAGE="retro"
if [[ "${COMMAND}" == "/fullsend" ]]; then
SECOND_WORD="$(echo "${COMMENT_BODY}" | head -1 | awk '{print $2}')"
if [[ "${SECOND_WORD}" == "retro" ]]; then
STAGE="retro"
fi
else
STAGE="retro"
fi
fi
;;
/fs-prioritize)
/prioritize)
if [[ "${COMMENT_USER_TYPE}" != "Bot" ]] && is_authorized; then
STAGE="prioritize"
fi
Expand Down Expand Up @@ -158,8 +166,14 @@ jobs:
# Check no-fix label (use PR_LABELS — issue.labels is empty
# on pull_request_review events)
if ! has_label "fullsend-no-fix" "${PR_LABELS}"; then
STAGE="fix"
TRIGGER_SOURCE="${REVIEW_USER_LOGIN}"
# Human PRs require the fullsend-fix label to auto-trigger
# the fix agent. The /fs-fix slash command intentionally
# bypasses this gate — authorized users can always trigger
# fix manually regardless of labels.
if [[ "${PR_USER_LOGIN}" =~ \[bot\]$ ]] || has_label "fullsend-fix" "${PR_LABELS}"; then
STAGE="fix"
TRIGGER_SOURCE="${REVIEW_USER_LOGIN}"
fi
fi
fi
fi
Expand Down Expand Up @@ -256,7 +270,8 @@ jobs:
set -euo pipefail
STAGE_ROLE="$STAGE"
case "$STAGE" in
code|fix) STAGE_ROLE="coder" ;;
code) STAGE_ROLE="coder" ;;
retro|prioritize) STAGE_ROLE="fullsend" ;;
esac

ROLES=$(yq '.defaults.roles[]' config.yaml 2>/dev/null || echo "")
Expand Down
Loading