Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 6aea7d0

Browse files
Merge branch 'dev' into feat/add-unbound-provider
2 parents 1cc3ecf + aedd760 commit 6aea7d0

556 files changed

Lines changed: 44428 additions & 7887 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Close stale PRs
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
dryRun:
7+
description: "Log actions without closing PRs"
8+
type: boolean
9+
default: false
10+
schedule:
11+
- cron: "0 6 * * *"
12+
13+
permissions:
14+
contents: read
15+
issues: write
16+
pull-requests: write
17+
18+
jobs:
19+
close-stale-prs:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- name: Close inactive PRs
23+
uses: actions/github-script@v8
24+
with:
25+
github-token: ${{ secrets.GITHUB_TOKEN }}
26+
script: |
27+
const DAYS_INACTIVE = 60
28+
const cutoff = new Date(Date.now() - DAYS_INACTIVE * 24 * 60 * 60 * 1000)
29+
const { owner, repo } = context.repo
30+
const dryRun = context.payload.inputs?.dryRun === "true"
31+
const stalePrs = []
32+
33+
core.info(`Dry run mode: ${dryRun}`)
34+
35+
const prs = await github.paginate(github.rest.pulls.list, {
36+
owner,
37+
repo,
38+
state: "open",
39+
per_page: 100,
40+
sort: "updated",
41+
direction: "asc",
42+
})
43+
44+
for (const pr of prs) {
45+
const lastUpdated = new Date(pr.updated_at)
46+
if (lastUpdated > cutoff) {
47+
core.info(`PR ${pr.number} is fresh`)
48+
continue
49+
}
50+
51+
stalePrs.push(pr)
52+
}
53+
54+
if (!stalePrs.length) {
55+
core.info("No stale pull requests found.")
56+
return
57+
}
58+
59+
for (const pr of stalePrs) {
60+
const issue_number = pr.number
61+
const closeComment = `Closing this pull request because it has had no updates for more than ${DAYS_INACTIVE} days. If you plan to continue working on it, feel free to reopen or open a new PR.`
62+
63+
if (dryRun) {
64+
core.info(`[dry-run] Would close PR #${issue_number} from ${pr.user.login}`)
65+
continue
66+
}
67+
68+
await github.rest.issues.createComment({
69+
owner,
70+
repo,
71+
issue_number,
72+
body: closeComment,
73+
})
74+
75+
await github.rest.pulls.update({
76+
owner,
77+
repo,
78+
pull_number: issue_number,
79+
state: "closed",
80+
})
81+
82+
core.info(`Closed PR #${issue_number} from ${pr.user.login}`)
83+
}
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"

0 commit comments

Comments
 (0)