summaryrefslogtreecommitdiffhomepage
path: root/.github
diff options
context:
space:
mode:
authorRyan Vogel <[email protected]>2026-01-21 18:35:22 -0500
committerGitHub <[email protected]>2026-01-21 18:35:22 -0500
commita0d71bf8ef6df0d58ccb9aa458e028ce8204c26a (patch)
tree5a132779dcfd01a435e0e4b3145a40cbdb0a4a3c /.github
parent19fe3e265ad59e7e65df4b3d86b09ec88f454bde (diff)
downloadopencode-a0d71bf8ef6df0d58ccb9aa458e028ce8204c26a.tar.gz
opencode-a0d71bf8ef6df0d58ccb9aa458e028ce8204c26a.zip
feat: add daily Discord recaps for issues and PRs (#9904)
Diffstat (limited to '.github')
-rw-r--r--.github/workflows/daily-issues-recap.yml166
-rw-r--r--.github/workflows/daily-pr-recap.yml174
2 files changed, 340 insertions, 0 deletions
diff --git a/.github/workflows/daily-issues-recap.yml b/.github/workflows/daily-issues-recap.yml
new file mode 100644
index 000000000..a333e5365
--- /dev/null
+++ b/.github/workflows/daily-issues-recap.yml
@@ -0,0 +1,166 @@
+name: Daily Issues Recap
+
+on:
+ schedule:
+ # Run at 6 PM EST (23:00 UTC, or 22:00 UTC during daylight saving)
+ - cron: "0 23 * * *"
+ workflow_dispatch: # Allow manual trigger for testing
+
+jobs:
+ daily-recap:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: read
+ issues: read
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Install opencode
+ run: curl -fsSL https://opencode.ai/install | bash
+
+ - name: Generate daily issues recap
+ id: recap
+ env:
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ OPENCODE_PERMISSION: |
+ {
+ "bash": {
+ "*": "deny",
+ "gh issue*": "allow",
+ "gh search*": "allow"
+ },
+ "webfetch": "deny",
+ "edit": "deny",
+ "write": "deny"
+ }
+ run: |
+ # Get today's date range
+ TODAY=$(date -u +%Y-%m-%d)
+
+ opencode run -m opencode/claude-sonnet-4-5 "Generate a daily issues recap for the OpenCode repository.
+
+ TODAY'S DATE: ${TODAY}
+
+ STEP 1: Gather today's issues
+ Search for all issues created today (${TODAY}) using:
+ gh issue list --repo ${{ github.repository }} --state all --search \"created:${TODAY}\" --json number,title,body,labels,state,comments,createdAt,author --limit 500
+
+ STEP 2: Analyze and categorize
+ For each issue created today, categorize it:
+
+ **Severity Assessment:**
+ - CRITICAL: Crashes, data loss, security issues, blocks major functionality
+ - HIGH: Significant bugs affecting many users, important features broken
+ - MEDIUM: Bugs with workarounds, minor features broken
+ - LOW: Minor issues, cosmetic, nice-to-haves
+
+ **Activity Assessment:**
+ - Note issues with high comment counts or engagement
+ - Note issues from repeat reporters (check if author has filed before)
+
+ STEP 3: Cross-reference with existing issues
+ For issues that seem like feature requests or recurring bugs:
+ - Search for similar older issues to identify patterns
+ - Note if this is a frequently requested feature
+ - Identify any issues that are duplicates of long-standing requests
+
+ STEP 4: Generate the recap
+ Create a structured recap with these sections:
+
+ ===DISCORD_START===
+ **Daily Issues Recap - ${TODAY}**
+
+ **Summary Stats**
+ - Total issues opened today: [count]
+ - By category: [bugs/features/questions]
+
+ **Critical/High Priority Issues**
+ [List any CRITICAL or HIGH severity issues with brief descriptions and issue numbers]
+
+ **Most Active/Discussed**
+ [Issues with significant engagement or from active community members]
+
+ **Trending Topics**
+ [Patterns noticed - e.g., 'Multiple reports about X', 'Continued interest in Y feature']
+
+ **Duplicates & Related**
+ [Issues that relate to existing open issues]
+ ===DISCORD_END===
+
+ STEP 5: Format for Discord
+ Format the recap as a Discord-compatible message:
+ - Use Discord markdown (**, __, etc.)
+ - BE EXTREMELY CONCISE - this is an EOD summary, not a detailed report
+ - Use hyperlinked issue numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/issues/1234>)
+ - Group related issues on single lines where possible
+ - Add emoji sparingly for critical items only
+ - HARD LIMIT: Keep under 1800 characters total
+ - Skip sections that have nothing notable (e.g., if no critical issues, omit that section)
+ - Prioritize signal over completeness - only surface what matters
+
+ OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/recap_raw.txt
+
+ # Extract only the Discord message between markers
+ sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/recap_raw.txt | grep -v '===DISCORD' > /tmp/recap.txt
+
+ echo "recap_file=/tmp/recap.txt" >> $GITHUB_OUTPUT
+
+ - name: Post to Discord
+ env:
+ DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
+ run: |
+ if [ -z "$DISCORD_WEBHOOK_URL" ]; then
+ echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
+ cat /tmp/recap.txt
+ exit 0
+ fi
+
+ # Read the recap
+ RECAP_RAW=$(cat /tmp/recap.txt)
+ RECAP_LENGTH=${#RECAP_RAW}
+
+ echo "Recap length: ${RECAP_LENGTH} chars"
+
+ # Function to post a message to Discord
+ post_to_discord() {
+ local msg="$1"
+ local content=$(echo "$msg" | jq -Rs '.')
+ curl -s -H "Content-Type: application/json" \
+ -X POST \
+ -d "{\"content\": ${content}}" \
+ "$DISCORD_WEBHOOK_URL"
+ sleep 1
+ }
+
+ # If under limit, send as single message
+ if [ "$RECAP_LENGTH" -le 1950 ]; then
+ post_to_discord "$RECAP_RAW"
+ else
+ echo "Splitting into multiple messages..."
+ remaining="$RECAP_RAW"
+ while [ ${#remaining} -gt 0 ]; do
+ if [ ${#remaining} -le 1950 ]; then
+ post_to_discord "$remaining"
+ break
+ else
+ chunk="${remaining:0:1900}"
+ last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
+ if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
+ chunk="${remaining:0:$last_newline}"
+ remaining="${remaining:$((last_newline+1))}"
+ else
+ chunk="${remaining:0:1900}"
+ remaining="${remaining:1900}"
+ fi
+ post_to_discord "$chunk"
+ fi
+ done
+ fi
+
+ echo "Posted daily recap to Discord"
diff --git a/.github/workflows/daily-pr-recap.yml b/.github/workflows/daily-pr-recap.yml
new file mode 100644
index 000000000..5d9597f77
--- /dev/null
+++ b/.github/workflows/daily-pr-recap.yml
@@ -0,0 +1,174 @@
+name: Daily PR Recap
+
+on:
+ schedule:
+ # Run at 5pm EST (22:00 UTC, or 21:00 UTC during daylight saving)
+ - cron: "0 22 * * *"
+ workflow_dispatch: # Allow manual trigger for testing
+
+jobs:
+ pr-recap:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: read
+ pull-requests: read
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Install opencode
+ run: curl -fsSL https://opencode.ai/install | bash
+
+ - name: Generate daily PR recap
+ id: recap
+ env:
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ OPENCODE_PERMISSION: |
+ {
+ "bash": {
+ "*": "deny",
+ "gh pr*": "allow",
+ "gh search*": "allow"
+ },
+ "webfetch": "deny",
+ "edit": "deny",
+ "write": "deny"
+ }
+ run: |
+ TODAY=$(date -u +%Y-%m-%d)
+
+ opencode run -m opencode/claude-sonnet-4-5 "Generate a daily PR activity recap for the OpenCode repository.
+
+ TODAY'S DATE: ${TODAY}
+
+ STEP 1: Gather PR data
+ Run these commands to gather PR information:
+
+ # Open PRs with bug fix labels or 'fix' in title
+ gh pr list --repo ${{ github.repository }} --state open --search \"fix in:title\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100
+
+ # PRs with high activity (get comments separately to filter bots)
+ gh pr list --repo ${{ github.repository }} --state open --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft --limit 100
+
+ # Recently merged bug fixes
+ gh pr list --repo ${{ github.repository }} --state merged --search \"merged:${TODAY} fix in:title\" --json number,title,author,mergedAt --limit 50
+
+ STEP 2: For high-activity PRs, check comment counts
+ For promising PRs, run:
+ gh pr view [NUMBER] --repo ${{ github.repository }} --json comments --jq '[.comments[] | select(.author.login != \"copilot-pull-request-reviewer\" and .author.login != \"github-actions\")] | length'
+
+ IMPORTANT: When counting comments/activity, EXCLUDE these bot accounts:
+ - copilot-pull-request-reviewer
+ - github-actions
+
+ STEP 3: Identify what matters
+
+ **Bug Fixes We Might Miss:**
+ - PRs with 'fix' or 'bug' in title that have been open 2+ days
+ - Small bug fixes (< 100 lines changed) that are easy to review
+ - Bug fixes from community contributors (not core team)
+
+ **High Activity PRs:**
+ - PRs with 5+ human comments (excluding bots listed above)
+ - PRs with back-and-forth discussion
+ - Controversial or complex changes getting attention
+
+ **Quick Wins:**
+ - Small PRs (< 50 lines) that are approved or nearly approved
+ - Bug fixes that just need a final review
+
+ STEP 4: Generate the recap
+ Create a structured recap:
+
+ ===DISCORD_START===
+ **Daily PR Recap - ${TODAY}**
+
+ **Bug Fixes Needing Attention**
+ [PRs fixing bugs that might be overlooked - prioritize by age and size]
+
+ **High Activity** (5+ human comments)
+ [PRs with significant discussion - exclude bot comments]
+
+ **Quick Wins** (small, ready to merge)
+ [Easy PRs that just need a review/merge]
+
+ **Merged Bug Fixes Today**
+ [What bug fixes shipped]
+ ===DISCORD_END===
+
+ STEP 5: Format for Discord
+ - Use Discord markdown (**, __, etc.)
+ - BE EXTREMELY CONCISE - surface what we might miss
+ - Use hyperlinked PR numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/pull/1234>)
+ - Include PR author: [#1234](<url>) (@author)
+ - For bug fixes, add brief description of what it fixes
+ - Show line count for quick wins: \"(+15/-3 lines)\"
+ - HARD LIMIT: Keep under 1800 characters total
+ - Skip empty sections
+ - Focus on PRs that need human eyes
+
+ OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/pr_recap_raw.txt
+
+ # Extract only the Discord message between markers
+ sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/pr_recap_raw.txt | grep -v '===DISCORD' > /tmp/pr_recap.txt
+
+ echo "recap_file=/tmp/pr_recap.txt" >> $GITHUB_OUTPUT
+
+ - name: Post to Discord
+ env:
+ DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
+ run: |
+ if [ -z "$DISCORD_WEBHOOK_URL" ]; then
+ echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
+ cat /tmp/pr_recap.txt
+ exit 0
+ fi
+
+ # Read the recap
+ RECAP_RAW=$(cat /tmp/pr_recap.txt)
+ RECAP_LENGTH=${#RECAP_RAW}
+
+ echo "Recap length: ${RECAP_LENGTH} chars"
+
+ # Function to post a message to Discord
+ post_to_discord() {
+ local msg="$1"
+ local content=$(echo "$msg" | jq -Rs '.')
+ curl -s -H "Content-Type: application/json" \
+ -X POST \
+ -d "{\"content\": ${content}}" \
+ "$DISCORD_WEBHOOK_URL"
+ sleep 1
+ }
+
+ # If under limit, send as single message
+ if [ "$RECAP_LENGTH" -le 1950 ]; then
+ post_to_discord "$RECAP_RAW"
+ else
+ echo "Splitting into multiple messages..."
+ remaining="$RECAP_RAW"
+ while [ ${#remaining} -gt 0 ]; do
+ if [ ${#remaining} -le 1950 ]; then
+ post_to_discord "$remaining"
+ break
+ else
+ chunk="${remaining:0:1900}"
+ last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
+ if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
+ chunk="${remaining:0:$last_newline}"
+ remaining="${remaining:$((last_newline+1))}"
+ else
+ chunk="${remaining:0:1900}"
+ remaining="${remaining:1900}"
+ fi
+ post_to_discord "$chunk"
+ fi
+ done
+ fi
+
+ echo "Posted daily PR recap to Discord"