chore: import upstream snapshot with attribution
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
name: Triage Issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue-number:
|
||||
description: "Issue number to triage"
|
||||
required: true
|
||||
type: number
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
# Writes (comment + labels) are performed with GH_ACCESS_TOKEN (the
|
||||
# workers-devprod bot) so they are attributed to that identity, not
|
||||
# github-actions[bot]. Only read access is needed from the default token.
|
||||
issues: read
|
||||
|
||||
# Cancel in-progress runs for the same issue
|
||||
concurrency:
|
||||
group: triage-${{ github.event.issue.number || inputs.issue-number }}
|
||||
cancel-in-progress: ${{ github.head_ref != 'changeset-release/main' }}
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
# Always run for manual dispatch. For issue events, skip PRs and
|
||||
# bot-created issues. For comment events, skip PRs, bot comments, and the
|
||||
# triage bot's own sticky comment (identified by the marocchino marker) to
|
||||
# avoid infinite re-triage loops.
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'issues' &&
|
||||
!github.event.issue.pull_request &&
|
||||
github.event.issue.user.type != 'Bot') ||
|
||||
(github.event_name == 'issue_comment' &&
|
||||
!github.event.issue.pull_request &&
|
||||
github.event.comment.user.type != 'Bot' &&
|
||||
!contains(github.event.comment.body, '<!-- Sticky Pull Request Comment'))
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Resolve issue number
|
||||
id: issue
|
||||
env:
|
||||
EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
INPUT_ISSUE_NUMBER: ${{ inputs.issue-number }}
|
||||
run: |
|
||||
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
|
||||
echo "number=${INPUT_ISSUE_NUMBER}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "number=${EVENT_ISSUE_NUMBER}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Checkout (sparse — just the skill and opencode config)
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
sparse-checkout: |
|
||||
.github/skills
|
||||
.github/opencode.json
|
||||
|
||||
- name: Install OpenCode
|
||||
run: |
|
||||
npm install -g opencode-ai
|
||||
cp .github/opencode.json ./opencode.json
|
||||
|
||||
- name: Fetch issue data
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
ISSUE: ${{ steps.issue.outputs.number }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
mkdir -p "data/${ISSUE}"
|
||||
|
||||
gh issue view "$ISSUE" \
|
||||
--repo "$REPO" \
|
||||
--json number,title,body,comments,createdAt,updatedAt,labels,state,author \
|
||||
> "data/${ISSUE}/context.json"
|
||||
|
||||
- name: Analyze issue with OpenCode
|
||||
env:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_AI_GATEWAY_ACCOUNT_ID }}
|
||||
CLOUDFLARE_GATEWAY_ID: ${{ secrets.CF_AI_GATEWAY_NAME }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_AI_GATEWAY_TOKEN }}
|
||||
ISSUE: ${{ steps.issue.outputs.number }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
opencode run \
|
||||
--print-logs \
|
||||
"Analyze GitHub issue ${REPO}#${ISSUE} using the skill at .github/skills/issue-review.md.
|
||||
|
||||
The issue data has been pre-fetched to data/${ISSUE}/context.json.
|
||||
Read it with the read tool and follow the triage process.
|
||||
|
||||
Create:
|
||||
- data/${ISSUE}/report.md (full markdown report)
|
||||
- data/${ISSUE}/summary.json (structured JSON summary)
|
||||
"
|
||||
|
||||
- name: Upload report to dashboard
|
||||
env:
|
||||
CF_ACCESS_CLIENT_ID: ${{ secrets.CF1_ACCESS_CLIENT_ID }}
|
||||
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF1_ACCESS_CLIENT_SECRET }}
|
||||
DASHBOARD_URL: ${{ secrets.REPORTS_DASHBOARD_URL }}
|
||||
ISSUE: ${{ steps.issue.outputs.number }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
# Check files were created
|
||||
if [ ! -f "data/${ISSUE}/report.md" ]; then
|
||||
echo "::error::report.md was not generated"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "data/${ISSUE}/summary.json" ]; then
|
||||
echo "::error::summary.json was not generated"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate the summary is parseable JSON before doing anything with it.
|
||||
if ! jq empty "data/${ISSUE}/summary.json" 2>/dev/null; then
|
||||
echo "::error::summary.json is not valid JSON"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the dashboard payload from the structured summary + markdown report.
|
||||
# suggestedComment is kept as a "Yes"/"No" string for dashboard compatibility.
|
||||
PAYLOAD=$(jq \
|
||||
--arg githubUrl "https://github.com/${REPO}/issues/${ISSUE}" \
|
||||
--rawfile reportMarkdown "data/${ISSUE}/report.md" \
|
||||
'{
|
||||
title: .title,
|
||||
githubUrl: $githubUrl,
|
||||
recommendation: .recommendation,
|
||||
difficulty: .difficulty,
|
||||
reasoning: .reasoning,
|
||||
suggestedAction: .suggestedAction,
|
||||
suggestedComment: (if .hasSuggestedComment then "Yes" else "No" end),
|
||||
reportMarkdown: $reportMarkdown
|
||||
}' "data/${ISSUE}/summary.json")
|
||||
|
||||
# POST to dashboard API
|
||||
HTTP_STATUS=$(curl -L --post302 -s -o response.json -w "%{http_code}" \
|
||||
-X POST "${DASHBOARD_URL}/api/report/${ISSUE}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "CF-Access-Client-Id: ${CF_ACCESS_CLIENT_ID}" \
|
||||
-H "CF-Access-Client-Secret: ${CF_ACCESS_CLIENT_SECRET}" \
|
||||
-d "$PAYLOAD")
|
||||
|
||||
echo "Dashboard API response: HTTP ${HTTP_STATUS}"
|
||||
cat response.json
|
||||
|
||||
if [ "$HTTP_STATUS" != "200" ]; then
|
||||
echo "::error::Failed to upload report (HTTP ${HTTP_STATUS})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Report uploaded for issue #${ISSUE}"
|
||||
|
||||
- name: Build triage comment
|
||||
env:
|
||||
ISSUE: ${{ steps.issue.outputs.number }}
|
||||
run: |
|
||||
{
|
||||
echo "> [!NOTE]"
|
||||
echo "> This is an automated, advisory triage report generated by workers-devprod. It is not an official maintainer response — a maintainer will follow up."
|
||||
echo ""
|
||||
echo "<details>"
|
||||
echo "<summary>🤖 Automated triage report</summary>"
|
||||
echo ""
|
||||
cat "data/${ISSUE}/report.md"
|
||||
echo ""
|
||||
echo "</details>"
|
||||
echo ""
|
||||
echo "<details>"
|
||||
echo "<summary>Structured summary (JSON)</summary>"
|
||||
echo ""
|
||||
echo '```json'
|
||||
cat "data/${ISSUE}/summary.json"
|
||||
echo ""
|
||||
echo '```'
|
||||
echo ""
|
||||
echo "</details>"
|
||||
} > "data/${ISSUE}/comment.md"
|
||||
|
||||
- name: Post triage comment
|
||||
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3.0.3
|
||||
with:
|
||||
header: triage-report
|
||||
number: ${{ steps.issue.outputs.number }}
|
||||
path: data/${{ steps.issue.outputs.number }}/comment.md
|
||||
GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||
|
||||
- name: Apply suggested labels
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||
ISSUE: ${{ steps.issue.outputs.number }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
SUMMARY_PATH="data/${ISSUE}/summary.json"
|
||||
if [ ! -f "$SUMMARY_PATH" ]; then
|
||||
echo "No summary.json; skipping label application"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# suggestedLabels is a JSON array; emit one label per line.
|
||||
jq -r '.suggestedLabels // [] | .[]' "$SUMMARY_PATH" > suggested-labels.txt
|
||||
if [ ! -s suggested-labels.txt ]; then
|
||||
echo "No suggested labels"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Only apply labels that actually exist in the repo.
|
||||
gh label list --repo "$REPO" --limit 500 --json name --jq '.[].name' > repo-labels.txt
|
||||
|
||||
ARGS=()
|
||||
while IFS= read -r label; do
|
||||
if [ -z "$label" ]; then
|
||||
continue
|
||||
fi
|
||||
if grep -Fxq "$label" repo-labels.txt; then
|
||||
ARGS+=(--add-label "$label")
|
||||
else
|
||||
echo "Skipping unknown label: ${label}"
|
||||
fi
|
||||
done < suggested-labels.txt
|
||||
|
||||
if [ ${#ARGS[@]} -eq 0 ]; then
|
||||
echo "No valid labels to apply"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
gh issue edit "$ISSUE" --repo "$REPO" "${ARGS[@]}"
|
||||
Reference in New Issue
Block a user