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
179 changes: 149 additions & 30 deletions .github/workflows/fullsend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,19 @@ permissions:

on:
issues:
types: [labeled, opened]
types: [labeled]
issue_comment:
types: [created]
pull_request_target:
types: [opened, synchronize, ready_for_review]
types: [opened, synchronize, ready_for_review, closed]
pull_request_review:
types: [submitted]

jobs:
dispatch-triage:
runs-on: ubuntu-latest
outputs:
agent: ${{ steps.dispatch.outputs.agent }}
concurrency:
group: triage-${{ github.event.issue.number || github.event.pull_request.number }}
cancel-in-progress: true
Expand Down Expand Up @@ -69,22 +71,27 @@ jobs:
'{issue: {number: (if $in != "" then ($in | tonumber) else null end), html_url: (if $iu != "" then $iu else null end)}}')
echo "json=$PAYLOAD" >> "$GITHUB_OUTPUT"
- name: Dispatch triage stage
id: dispatch
env:
GH_TOKEN: ${{ secrets.FULLSEND_DISPATCH_TOKEN }}
EVENT_PAYLOAD: ${{ steps.payload.outputs.json }}
EVENT_TYPE: ${{ github.event_name }}
SOURCE_REPO: ${{ github.repository }}
DISPATCH_REPO: ${{ github.repository_owner }}/.fullsend
run: |
gh workflow run dispatch.yml \
DISPATCH_URL=$(gh workflow run dispatch.yml \
--repo "$DISPATCH_REPO" \
-f stage=triage \
-f event_type="$EVENT_TYPE" \
-f source_repo="$SOURCE_REPO" \
-f event_payload="$EVENT_PAYLOAD"
-f event_payload="$EVENT_PAYLOAD")
echo "::notice::Dispatched triage → ${DISPATCH_URL}"
echo "agent=triage" >> "$GITHUB_OUTPUT"

dispatch-code:
runs-on: ubuntu-latest
outputs:
agent: ${{ steps.dispatch.outputs.agent }}
if: >-
(github.event_name == 'issues' && github.event.action == 'labeled'
&& github.event.label.name == 'ready-to-code') ||
Expand All @@ -109,22 +116,27 @@ jobs:
'{issue: {number: (if $in != "" then ($in | tonumber) else null end), html_url: (if $iu != "" then $iu else null end)}}')
echo "json=$PAYLOAD" >> "$GITHUB_OUTPUT"
- name: Dispatch code stage
id: dispatch
env:
GH_TOKEN: ${{ secrets.FULLSEND_DISPATCH_TOKEN }}
EVENT_PAYLOAD: ${{ steps.payload.outputs.json }}
EVENT_TYPE: ${{ github.event_name }}
SOURCE_REPO: ${{ github.repository }}
DISPATCH_REPO: ${{ github.repository_owner }}/.fullsend
run: |
gh workflow run dispatch.yml \
DISPATCH_URL=$(gh workflow run dispatch.yml \
--repo "$DISPATCH_REPO" \
-f stage=code \
-f event_type="$EVENT_TYPE" \
-f source_repo="$SOURCE_REPO" \
-f event_payload="$EVENT_PAYLOAD"
-f event_payload="$EVENT_PAYLOAD")
echo "::notice::Dispatched code → ${DISPATCH_URL}"
echo "agent=code" >> "$GITHUB_OUTPUT"

dispatch-review:
runs-on: ubuntu-latest
outputs:
agent: ${{ steps.dispatch.outputs.agent }}
# The review agent is NOT dispatched on pull_request_review events.
# Bot changes_requested reviews dispatch the fix agent (dispatch-fix-bot).
# All other review submissions (human approve/comment/etc.) are ignored —
Expand All @@ -137,7 +149,8 @@ jobs:
startsWith(github.event.comment.body, '/review ') ||
startsWith(github.event.comment.body, format('{0}{1}', '/review', fromJSON('"\n"')))
)) ||
github.event_name == 'pull_request_target'
(github.event_name == 'pull_request_target'
&& github.event.action != 'closed')
steps:
- name: Build minimal payload
id: payload
Expand All @@ -157,22 +170,27 @@ jobs:
pull_request: {number: (if $pn != "" then ($pn | tonumber) else null end), html_url: (if $pu != "" then $pu else null end)}}')
echo "json=$PAYLOAD" >> "$GITHUB_OUTPUT"
- name: Dispatch review stage
id: dispatch
env:
GH_TOKEN: ${{ secrets.FULLSEND_DISPATCH_TOKEN }}
EVENT_PAYLOAD: ${{ steps.payload.outputs.json }}
EVENT_TYPE: ${{ github.event_name }}
SOURCE_REPO: ${{ github.repository }}
DISPATCH_REPO: ${{ github.repository_owner }}/.fullsend
run: |
gh workflow run dispatch.yml \
DISPATCH_URL=$(gh workflow run dispatch.yml \
--repo "$DISPATCH_REPO" \
-f stage=review \
-f event_type="$EVENT_TYPE" \
-f source_repo="$SOURCE_REPO" \
-f event_payload="$EVENT_PAYLOAD"
-f event_payload="$EVENT_PAYLOAD")
echo "::notice::Dispatched review → ${DISPATCH_URL}"
echo "agent=review" >> "$GITHUB_OUTPUT"

dispatch-fix-bot:
runs-on: ubuntu-latest
outputs:
agent: ${{ steps.dispatch.outputs.agent }}
concurrency:
group: fix-${{ github.event.pull_request.number }}
cancel-in-progress: true
Expand Down Expand Up @@ -202,6 +220,7 @@ jobs:
'{pull_request: {number: ($pn | tonumber), head: {ref: $hr, repo: {full_name: $hrepo}}, base: {ref: $br, repo: {full_name: $brepo}}}}')
echo "json=$PAYLOAD" >> "$GITHUB_OUTPUT"
- name: Dispatch fix stage (bot-triggered)
id: dispatch
env:
GH_TOKEN: ${{ secrets.FULLSEND_DISPATCH_TOKEN }}
EVENT_PAYLOAD: ${{ steps.payload.outputs.json }}
Expand All @@ -210,16 +229,20 @@ jobs:
DISPATCH_REPO: ${{ github.repository_owner }}/.fullsend
TRIGGER_USER: ${{ github.event.review.user.login }}
run: |
gh workflow run dispatch.yml \
DISPATCH_URL=$(gh workflow run dispatch.yml \
--repo "$DISPATCH_REPO" \
-f stage=fix \
-f event_type="$EVENT_TYPE" \
-f source_repo="$SOURCE_REPO" \
-f event_payload="$EVENT_PAYLOAD" \
-f trigger_source="$TRIGGER_USER"
-f trigger_source="$TRIGGER_USER")
echo "::notice::Dispatched fix (bot) → ${DISPATCH_URL}"
echo "agent=fix" >> "$GITHUB_OUTPUT"

dispatch-fix-human:
runs-on: ubuntu-latest
outputs:
agent: ${{ steps.dispatch.outputs.agent }}
concurrency:
group: fix-${{ github.event.issue.number }}
cancel-in-progress: true
Expand Down Expand Up @@ -262,6 +285,7 @@ jobs:
'{issue: {number: ($in | tonumber)}, comment: {body: $cb}}')
echo "json=$PAYLOAD" >> "$GITHUB_OUTPUT"
- name: Dispatch fix stage (human-triggered)
id: dispatch
env:
GH_TOKEN: ${{ secrets.FULLSEND_DISPATCH_TOKEN }}
EVENT_PAYLOAD: ${{ steps.payload.outputs.json }}
Expand All @@ -270,50 +294,110 @@ jobs:
DISPATCH_REPO: ${{ github.repository_owner }}/.fullsend
TRIGGER_USER: ${{ github.event.comment.user.login }}
run: |
gh workflow run dispatch.yml \
DISPATCH_URL=$(gh workflow run dispatch.yml \
--repo "$DISPATCH_REPO" \
-f stage=fix \
-f event_type="$EVENT_TYPE" \
-f source_repo="$SOURCE_REPO" \
-f event_payload="$EVENT_PAYLOAD" \
-f trigger_source="$TRIGGER_USER"
-f trigger_source="$TRIGGER_USER")
echo "::notice::Dispatched fix (human) → ${DISPATCH_URL}"
echo "agent=fix" >> "$GITHUB_OUTPUT"

dispatch-retro:
runs-on: ubuntu-latest
concurrency:
group: retro-${{ github.event.pull_request.number }}
cancel-in-progress: true
# Fires on all closed PRs (merged and abandoned). Retrospectives on
# abandoned PRs are valuable — they surface why work was discarded.
if: >-
github.event_name == 'pull_request_target'
&& github.event.action == 'closed'
steps:
- name: Build minimal payload
id: payload
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_HTML_URL: ${{ github.event.pull_request.html_url }}
run: |
set -euo pipefail
PAYLOAD=$(jq -cn \
--arg pn "${PR_NUMBER}" \
--arg pu "${PR_HTML_URL}" \
'{pull_request: {number: ($pn | tonumber), html_url: $pu}}')
echo "json=$PAYLOAD" >> "$GITHUB_OUTPUT"
- name: Dispatch retro stage
env:
GH_TOKEN: ${{ secrets.FULLSEND_DISPATCH_TOKEN }}
EVENT_PAYLOAD: ${{ steps.payload.outputs.json }}
EVENT_TYPE: ${{ github.event_name }}
SOURCE_REPO: ${{ github.repository }}
DISPATCH_REPO: ${{ github.repository_owner }}/.fullsend
run: |
gh workflow run dispatch.yml \
--repo "$DISPATCH_REPO" \
-f stage=retro \
-f event_type="$EVENT_TYPE" \
-f source_repo="$SOURCE_REPO" \
-f event_payload="$EVENT_PAYLOAD"

dispatch-gh-classify:
dispatch-retro-command:
runs-on: ubuntu-latest
concurrency:
group: retro-${{ github.event.issue.number }}
cancel-in-progress: true
if: >-
github.event_name == 'issues' && github.event.action == 'opened'
github.event_name == 'issue_comment'
&& github.event.comment.user.type != 'Bot'
&& (
github.event.comment.body == '/retro'
|| startsWith(github.event.comment.body, '/retro ')
|| startsWith(github.event.comment.body, format('{0}{1}', '/retro', fromJSON('"\n"')))
|| github.event.comment.body == '/fullsend retro'
|| startsWith(github.event.comment.body, '/fullsend retro ')
|| startsWith(github.event.comment.body, format('{0}{1}', '/fullsend retro', fromJSON('"\n"')))
)
&& (
github.event.comment.author_association == 'OWNER'
|| github.event.comment.author_association == 'MEMBER'
|| github.event.comment.author_association == 'COLLABORATOR'
)
steps:
- name: Build minimal payload
id: payload
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_HTML_URL: ${{ github.event.issue.html_url }}
ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
# For PR comments, issue.pull_request.html_url gives the /pull/N URL.
# For issue comments, this is empty.
PR_HTML_URL: ${{ github.event.issue.pull_request.html_url || '' }}
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
set -euo pipefail
# Use PR URL if available (comment on a PR), otherwise issue URL.
ORIGINATING_URL="${PR_HTML_URL:-${ISSUE_HTML_URL}}"
PAYLOAD=$(jq -cn \
--arg in "${ISSUE_NUMBER:-}" \
--arg iu "${ISSUE_HTML_URL:-}" \
--arg au "${ISSUE_AUTHOR:-}" \
--arg repo "${{ github.repository }}" \
'{issue: {number: (if $in != "" then ($in | tonumber) else null end), html_url: (if $iu != "" then $iu else null end), author: $au}, repository: $repo}')
--arg in "${ISSUE_NUMBER}" \
--arg ou "${ORIGINATING_URL}" \
--arg cb "${COMMENT_BODY:-}" \
'{issue: {number: ($in | tonumber), html_url: $ou},
comment: {body: $cb}}')
echo "json=$PAYLOAD" >> "$GITHUB_OUTPUT"
- name: Dispatch gh-classify
- name: Dispatch retro stage
env:
GH_TOKEN: ${{ secrets.FULLSEND_DISPATCH_TOKEN }}
EVENT_PAYLOAD: ${{ steps.payload.outputs.json }}
EVENT_TYPE: issues
EVENT_TYPE: ${{ github.event_name }}
SOURCE_REPO: ${{ github.repository }}
DISPATCH_REPO: ${{ github.repository_owner }}/.fullsend
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
gh workflow run gh-classify.yml \
gh workflow run dispatch.yml \
--repo "$DISPATCH_REPO" \
--field event_type="$EVENT_TYPE" \
--field source_repo="$SOURCE_REPO" \
--field event_payload="$EVENT_PAYLOAD" \
--field classify_mode="single" \
--field issue_number="$ISSUE_NUMBER"
-f stage=retro \
-f event_type="$EVENT_TYPE" \
-f source_repo="$SOURCE_REPO" \
-f event_payload="$EVENT_PAYLOAD"

dispatch-stop-fix:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -347,3 +431,38 @@ jobs:
--add-label "fullsend-no-fix"
gh pr comment "$PR_NUMBER" --repo "$REPO" \
--body "Fix agent disabled for this PR. Remove the \`fullsend-no-fix\` label or use \`/fix\` to re-engage."

post-run-link:
permissions:
issues: write
runs-on: ubuntu-latest
if: always() && !cancelled()
needs:
[
dispatch-triage,
dispatch-code,
dispatch-review,
dispatch-fix-bot,
dispatch-fix-human,
]
env:
GH_TOKEN: ${{ github.token }}
steps:
- name: Post run link comment
env:
AGENT: >-
${{ needs.dispatch-triage.outputs.agent
|| needs.dispatch-code.outputs.agent
|| needs.dispatch-review.outputs.agent
|| needs.dispatch-fix-bot.outputs.agent
|| needs.dispatch-fix-human.outputs.agent }}
SHIM_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
ITEM_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
run: |
if [ -z "${AGENT}" ] || [ -z "${ITEM_NUMBER}" ]; then
echo "No dispatch or item number — skipping comment"
exit 0
fi
gh issue comment "${ITEM_NUMBER}" \
--repo "${GITHUB_REPOSITORY}" \
--body "**fullsend ${AGENT}** is working on this — [view logs](${SHIM_URL})"