Skip to content

Commit a0d71bf

Browse files
authored
feat: add daily Discord recaps for issues and PRs (anomalyco#9904)
1 parent 19fe3e2 commit a0d71bf

File tree

2 files changed

+340
-0
lines changed

2 files changed

+340
-0
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
name: Daily Issues Recap
2+
3+
on:
4+
schedule:
5+
# Run at 6 PM EST (23:00 UTC, or 22:00 UTC during daylight saving)
6+
- cron: "0 23 * * *"
7+
workflow_dispatch: # Allow manual trigger for testing
8+
9+
jobs:
10+
daily-recap:
11+
runs-on: blacksmith-4vcpu-ubuntu-2404
12+
permissions:
13+
contents: read
14+
issues: read
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 1
20+
21+
- uses: ./.github/actions/setup-bun
22+
23+
- name: Install opencode
24+
run: curl -fsSL https://opencode.ai/install | bash
25+
26+
- name: Generate daily issues recap
27+
id: recap
28+
env:
29+
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
30+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31+
OPENCODE_PERMISSION: |
32+
{
33+
"bash": {
34+
"*": "deny",
35+
"gh issue*": "allow",
36+
"gh search*": "allow"
37+
},
38+
"webfetch": "deny",
39+
"edit": "deny",
40+
"write": "deny"
41+
}
42+
run: |
43+
# Get today's date range
44+
TODAY=$(date -u +%Y-%m-%d)
45+
46+
opencode run -m opencode/claude-sonnet-4-5 "Generate a daily issues recap for the OpenCode repository.
47+
48+
TODAY'S DATE: ${TODAY}
49+
50+
STEP 1: Gather today's issues
51+
Search for all issues created today (${TODAY}) using:
52+
gh issue list --repo ${{ github.repository }} --state all --search \"created:${TODAY}\" --json number,title,body,labels,state,comments,createdAt,author --limit 500
53+
54+
STEP 2: Analyze and categorize
55+
For each issue created today, categorize it:
56+
57+
**Severity Assessment:**
58+
- CRITICAL: Crashes, data loss, security issues, blocks major functionality
59+
- HIGH: Significant bugs affecting many users, important features broken
60+
- MEDIUM: Bugs with workarounds, minor features broken
61+
- LOW: Minor issues, cosmetic, nice-to-haves
62+
63+
**Activity Assessment:**
64+
- Note issues with high comment counts or engagement
65+
- Note issues from repeat reporters (check if author has filed before)
66+
67+
STEP 3: Cross-reference with existing issues
68+
For issues that seem like feature requests or recurring bugs:
69+
- Search for similar older issues to identify patterns
70+
- Note if this is a frequently requested feature
71+
- Identify any issues that are duplicates of long-standing requests
72+
73+
STEP 4: Generate the recap
74+
Create a structured recap with these sections:
75+
76+
===DISCORD_START===
77+
**Daily Issues Recap - ${TODAY}**
78+
79+
**Summary Stats**
80+
- Total issues opened today: [count]
81+
- By category: [bugs/features/questions]
82+
83+
**Critical/High Priority Issues**
84+
[List any CRITICAL or HIGH severity issues with brief descriptions and issue numbers]
85+
86+
**Most Active/Discussed**
87+
[Issues with significant engagement or from active community members]
88+
89+
**Trending Topics**
90+
[Patterns noticed - e.g., 'Multiple reports about X', 'Continued interest in Y feature']
91+
92+
**Duplicates & Related**
93+
[Issues that relate to existing open issues]
94+
===DISCORD_END===
95+
96+
STEP 5: Format for Discord
97+
Format the recap as a Discord-compatible message:
98+
- Use Discord markdown (**, __, etc.)
99+
- BE EXTREMELY CONCISE - this is an EOD summary, not a detailed report
100+
- Use hyperlinked issue numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/issues/1234>)
101+
- Group related issues on single lines where possible
102+
- Add emoji sparingly for critical items only
103+
- HARD LIMIT: Keep under 1800 characters total
104+
- Skip sections that have nothing notable (e.g., if no critical issues, omit that section)
105+
- Prioritize signal over completeness - only surface what matters
106+
107+
OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/recap_raw.txt
108+
109+
# Extract only the Discord message between markers
110+
sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/recap_raw.txt | grep -v '===DISCORD' > /tmp/recap.txt
111+
112+
echo "recap_file=/tmp/recap.txt" >> $GITHUB_OUTPUT
113+
114+
- name: Post to Discord
115+
env:
116+
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
117+
run: |
118+
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
119+
echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
120+
cat /tmp/recap.txt
121+
exit 0
122+
fi
123+
124+
# Read the recap
125+
RECAP_RAW=$(cat /tmp/recap.txt)
126+
RECAP_LENGTH=${#RECAP_RAW}
127+
128+
echo "Recap length: ${RECAP_LENGTH} chars"
129+
130+
# Function to post a message to Discord
131+
post_to_discord() {
132+
local msg="$1"
133+
local content=$(echo "$msg" | jq -Rs '.')
134+
curl -s -H "Content-Type: application/json" \
135+
-X POST \
136+
-d "{\"content\": ${content}}" \
137+
"$DISCORD_WEBHOOK_URL"
138+
sleep 1
139+
}
140+
141+
# If under limit, send as single message
142+
if [ "$RECAP_LENGTH" -le 1950 ]; then
143+
post_to_discord "$RECAP_RAW"
144+
else
145+
echo "Splitting into multiple messages..."
146+
remaining="$RECAP_RAW"
147+
while [ ${#remaining} -gt 0 ]; do
148+
if [ ${#remaining} -le 1950 ]; then
149+
post_to_discord "$remaining"
150+
break
151+
else
152+
chunk="${remaining:0:1900}"
153+
last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
154+
if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
155+
chunk="${remaining:0:$last_newline}"
156+
remaining="${remaining:$((last_newline+1))}"
157+
else
158+
chunk="${remaining:0:1900}"
159+
remaining="${remaining:1900}"
160+
fi
161+
post_to_discord "$chunk"
162+
fi
163+
done
164+
fi
165+
166+
echo "Posted daily recap to Discord"
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
name: Daily PR Recap
2+
3+
on:
4+
schedule:
5+
# Run at 5pm EST (22:00 UTC, or 21:00 UTC during daylight saving)
6+
- cron: "0 22 * * *"
7+
workflow_dispatch: # Allow manual trigger for testing
8+
9+
jobs:
10+
pr-recap:
11+
runs-on: blacksmith-4vcpu-ubuntu-2404
12+
permissions:
13+
contents: read
14+
pull-requests: read
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 1
20+
21+
- uses: ./.github/actions/setup-bun
22+
23+
- name: Install opencode
24+
run: curl -fsSL https://opencode.ai/install | bash
25+
26+
- name: Generate daily PR recap
27+
id: recap
28+
env:
29+
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
30+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31+
OPENCODE_PERMISSION: |
32+
{
33+
"bash": {
34+
"*": "deny",
35+
"gh pr*": "allow",
36+
"gh search*": "allow"
37+
},
38+
"webfetch": "deny",
39+
"edit": "deny",
40+
"write": "deny"
41+
}
42+
run: |
43+
TODAY=$(date -u +%Y-%m-%d)
44+
45+
opencode run -m opencode/claude-sonnet-4-5 "Generate a daily PR activity recap for the OpenCode repository.
46+
47+
TODAY'S DATE: ${TODAY}
48+
49+
STEP 1: Gather PR data
50+
Run these commands to gather PR information:
51+
52+
# Open PRs with bug fix labels or 'fix' in title
53+
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
54+
55+
# PRs with high activity (get comments separately to filter bots)
56+
gh pr list --repo ${{ github.repository }} --state open --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft --limit 100
57+
58+
# Recently merged bug fixes
59+
gh pr list --repo ${{ github.repository }} --state merged --search \"merged:${TODAY} fix in:title\" --json number,title,author,mergedAt --limit 50
60+
61+
STEP 2: For high-activity PRs, check comment counts
62+
For promising PRs, run:
63+
gh pr view [NUMBER] --repo ${{ github.repository }} --json comments --jq '[.comments[] | select(.author.login != \"copilot-pull-request-reviewer\" and .author.login != \"github-actions\")] | length'
64+
65+
IMPORTANT: When counting comments/activity, EXCLUDE these bot accounts:
66+
- copilot-pull-request-reviewer
67+
- github-actions
68+
69+
STEP 3: Identify what matters
70+
71+
**Bug Fixes We Might Miss:**
72+
- PRs with 'fix' or 'bug' in title that have been open 2+ days
73+
- Small bug fixes (< 100 lines changed) that are easy to review
74+
- Bug fixes from community contributors (not core team)
75+
76+
**High Activity PRs:**
77+
- PRs with 5+ human comments (excluding bots listed above)
78+
- PRs with back-and-forth discussion
79+
- Controversial or complex changes getting attention
80+
81+
**Quick Wins:**
82+
- Small PRs (< 50 lines) that are approved or nearly approved
83+
- Bug fixes that just need a final review
84+
85+
STEP 4: Generate the recap
86+
Create a structured recap:
87+
88+
===DISCORD_START===
89+
**Daily PR Recap - ${TODAY}**
90+
91+
**Bug Fixes Needing Attention**
92+
[PRs fixing bugs that might be overlooked - prioritize by age and size]
93+
94+
**High Activity** (5+ human comments)
95+
[PRs with significant discussion - exclude bot comments]
96+
97+
**Quick Wins** (small, ready to merge)
98+
[Easy PRs that just need a review/merge]
99+
100+
**Merged Bug Fixes Today**
101+
[What bug fixes shipped]
102+
===DISCORD_END===
103+
104+
STEP 5: Format for Discord
105+
- Use Discord markdown (**, __, etc.)
106+
- BE EXTREMELY CONCISE - surface what we might miss
107+
- Use hyperlinked PR numbers with suppressed embeds: [#1234](<https://github.com/${{ github.repository }}/pull/1234>)
108+
- Include PR author: [#1234](<url>) (@author)
109+
- For bug fixes, add brief description of what it fixes
110+
- Show line count for quick wins: \"(+15/-3 lines)\"
111+
- HARD LIMIT: Keep under 1800 characters total
112+
- Skip empty sections
113+
- Focus on PRs that need human eyes
114+
115+
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
116+
117+
# Extract only the Discord message between markers
118+
sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/pr_recap_raw.txt | grep -v '===DISCORD' > /tmp/pr_recap.txt
119+
120+
echo "recap_file=/tmp/pr_recap.txt" >> $GITHUB_OUTPUT
121+
122+
- name: Post to Discord
123+
env:
124+
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
125+
run: |
126+
if [ -z "$DISCORD_WEBHOOK_URL" ]; then
127+
echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
128+
cat /tmp/pr_recap.txt
129+
exit 0
130+
fi
131+
132+
# Read the recap
133+
RECAP_RAW=$(cat /tmp/pr_recap.txt)
134+
RECAP_LENGTH=${#RECAP_RAW}
135+
136+
echo "Recap length: ${RECAP_LENGTH} chars"
137+
138+
# Function to post a message to Discord
139+
post_to_discord() {
140+
local msg="$1"
141+
local content=$(echo "$msg" | jq -Rs '.')
142+
curl -s -H "Content-Type: application/json" \
143+
-X POST \
144+
-d "{\"content\": ${content}}" \
145+
"$DISCORD_WEBHOOK_URL"
146+
sleep 1
147+
}
148+
149+
# If under limit, send as single message
150+
if [ "$RECAP_LENGTH" -le 1950 ]; then
151+
post_to_discord "$RECAP_RAW"
152+
else
153+
echo "Splitting into multiple messages..."
154+
remaining="$RECAP_RAW"
155+
while [ ${#remaining} -gt 0 ]; do
156+
if [ ${#remaining} -le 1950 ]; then
157+
post_to_discord "$remaining"
158+
break
159+
else
160+
chunk="${remaining:0:1900}"
161+
last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
162+
if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
163+
chunk="${remaining:0:$last_newline}"
164+
remaining="${remaining:$((last_newline+1))}"
165+
else
166+
chunk="${remaining:0:1900}"
167+
remaining="${remaining:1900}"
168+
fi
169+
post_to_discord "$chunk"
170+
fi
171+
done
172+
fi
173+
174+
echo "Posted daily PR recap to Discord"

0 commit comments

Comments
 (0)