Skip to content

Commit 27da9da

Browse files
authored
Merge branch 'dev' into dev
2 parents 1218711 + 2a20822 commit 27da9da

1,979 files changed

Lines changed: 380708 additions & 76837 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.

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# web + desktop packages
22
packages/app/ @adamdotdevin
33
packages/tauri/ @adamdotdevin
4+
packages/desktop/src-tauri/ @brendonovich
45
packages/desktop/ @adamdotdevin

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
blank_issues_enabled: true
1+
blank_issues_enabled: false
22
contact_links:
33
- name: 💬 Discord Community
44
url: https://discord.gg/opencode

.github/TEAM_MEMBERS

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
adamdotdevin
2+
Brendonovich
3+
fwang
4+
Hona
5+
iamdavidhill
6+
jayair
7+
jlongster
8+
kitlangton
9+
kommander
10+
MrMushrooooom
11+
nexxeln
12+
R44VC0RP
13+
rekram1-node
14+
RhysSullivan
15+
thdxr

.github/VOUCHED.td

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Vouched contributors for this project.
2+
#
3+
# See https://github.com/mitchellh/vouch for details.
4+
#
5+
# Syntax:
6+
# - One handle per line (without @), sorted alphabetically.
7+
# - Optional platform prefix: platform:username (e.g., github:user).
8+
# - Denounce with minus prefix: -username or -platform:username.
9+
# - Optional details after a space following the handle.
10+
adamdotdevin
11+
-agusbasari29 AI PR slop
12+
ariane-emory
13+
edemaine
14+
-florianleibert
15+
fwang
16+
iamdavidhill
17+
jayair
18+
kitlangton
19+
kommander
20+
r44vc0rp
21+
rekram1-node
22+
-spider-yamet clawdbot/llm psychosis, spam pinging the team
23+
thdxr

.github/actions/setup-bun/action.yml

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,33 @@ description: "Setup Bun with caching and install dependencies"
33
runs:
44
using: "composite"
55
steps:
6-
- name: Mount Bun Cache
7-
uses: useblacksmith/stickydisk@v1
6+
- name: Cache Bun dependencies
7+
uses: actions/cache@v4
88
with:
9-
key: ${{ github.repository }}-bun-cache
10-
path: ~/.bun
9+
path: ~/.bun/install/cache
10+
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
11+
restore-keys: |
12+
${{ runner.os }}-bun-
13+
14+
- name: Get baseline download URL
15+
id: bun-url
16+
shell: bash
17+
run: |
18+
if [ "$RUNNER_ARCH" = "X64" ]; then
19+
V=$(node -p "require('./package.json').packageManager.split('@')[1]")
20+
case "$RUNNER_OS" in
21+
macOS) OS=darwin ;;
22+
Linux) OS=linux ;;
23+
Windows) OS=windows ;;
24+
esac
25+
echo "url=https://github.com/oven-sh/bun/releases/download/bun-v${V}/bun-${OS}-x64-baseline.zip" >> "$GITHUB_OUTPUT"
26+
fi
1127
1228
- name: Setup Bun
1329
uses: oven-sh/setup-bun@v2
1430
with:
15-
bun-version-file: package.json
31+
bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }}
32+
bun-download-url: ${{ steps.bun-url.outputs.url }}
1633

1734
- name: Install dependencies
1835
run: bun install

.github/pull_request_template.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,29 @@
1+
### Issue for this PR
2+
3+
Closes #
4+
5+
### Type of change
6+
7+
- [ ] Bug fix
8+
- [ ] New feature
9+
- [ ] Refactor / code improvement
10+
- [ ] Documentation
11+
112
### What does this PR do?
213

3-
Please provide a description of the issue (if there is one), the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the pr.
14+
Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR.
415

516
**If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!**
617

718
### How did you verify your code works?
19+
20+
### Screenshots / recordings
21+
22+
_If this is a UI change, please include a screenshot or recording._
23+
24+
### Checklist
25+
26+
- [ ] I have tested my changes locally
27+
- [ ] I have not included unrelated changes in this PR
28+
29+
_If you do not follow this template your PR will be automatically rejected._

.github/workflows/beta.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ jobs:
1010
runs-on: blacksmith-4vcpu-ubuntu-2404
1111
permissions:
1212
contents: write
13+
pull-requests: write
1314
steps:
1415
- name: Checkout repository
1516
uses: actions/checkout@v4
@@ -26,7 +27,11 @@ jobs:
2627
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
2728
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
2829

30+
- name: Install OpenCode
31+
run: bun i -g opencode-ai
32+
2933
- name: Sync beta branch
3034
env:
3135
GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
36+
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
3237
run: bun script/beta.ts

.github/workflows/close-stale-prs.yml

Lines changed: 183 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,66 +18,218 @@ permissions:
1818
jobs:
1919
close-stale-prs:
2020
runs-on: ubuntu-latest
21+
timeout-minutes: 15
2122
steps:
2223
- name: Close inactive PRs
2324
uses: actions/github-script@v8
2425
with:
2526
github-token: ${{ secrets.GITHUB_TOKEN }}
2627
script: |
2728
const DAYS_INACTIVE = 60
29+
const MAX_RETRIES = 3
30+
31+
// Adaptive delay: fast for small batches, slower for large to respect
32+
// GitHub's 80 content-generating requests/minute limit
33+
const SMALL_BATCH_THRESHOLD = 10
34+
const SMALL_BATCH_DELAY_MS = 1000 // 1s for daily operations (≤10 PRs)
35+
const LARGE_BATCH_DELAY_MS = 2000 // 2s for backlog (>10 PRs) = ~30 ops/min, well under 80 limit
36+
37+
const startTime = Date.now()
2838
const cutoff = new Date(Date.now() - DAYS_INACTIVE * 24 * 60 * 60 * 1000)
2939
const { owner, repo } = context.repo
3040
const dryRun = context.payload.inputs?.dryRun === "true"
31-
const stalePrs = []
3241
3342
core.info(`Dry run mode: ${dryRun}`)
43+
core.info(`Cutoff date: ${cutoff.toISOString()}`)
3444
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-
})
45+
function sleep(ms) {
46+
return new Promise(resolve => setTimeout(resolve, ms))
47+
}
4348
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+
async function withRetry(fn, description = 'API call') {
50+
let lastError
51+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
52+
try {
53+
const result = await fn()
54+
return result
55+
} catch (error) {
56+
lastError = error
57+
const isRateLimited = error.status === 403 &&
58+
(error.message?.includes('rate limit') || error.message?.includes('secondary'))
59+
60+
if (!isRateLimited) {
61+
throw error
62+
}
63+
64+
// Parse retry-after header, default to 60 seconds
65+
const retryAfter = error.response?.headers?.['retry-after']
66+
? parseInt(error.response.headers['retry-after'])
67+
: 60
68+
69+
// Exponential backoff: retryAfter * 2^attempt
70+
const backoffMs = retryAfter * 1000 * Math.pow(2, attempt)
71+
72+
core.warning(`${description}: Rate limited (attempt ${attempt + 1}/${MAX_RETRIES}). Waiting ${backoffMs / 1000}s before retry...`)
73+
74+
await sleep(backoffMs)
75+
}
4976
}
77+
core.error(`${description}: Max retries (${MAX_RETRIES}) exceeded`)
78+
throw lastError
79+
}
80+
81+
const query = `
82+
query($owner: String!, $repo: String!, $cursor: String) {
83+
repository(owner: $owner, name: $repo) {
84+
pullRequests(first: 100, states: OPEN, after: $cursor) {
85+
pageInfo {
86+
hasNextPage
87+
endCursor
88+
}
89+
nodes {
90+
number
91+
title
92+
author {
93+
login
94+
}
95+
createdAt
96+
commits(last: 1) {
97+
nodes {
98+
commit {
99+
committedDate
100+
}
101+
}
102+
}
103+
comments(last: 1) {
104+
nodes {
105+
createdAt
106+
}
107+
}
108+
reviews(last: 1) {
109+
nodes {
110+
createdAt
111+
}
112+
}
113+
}
114+
}
115+
}
116+
}
117+
`
118+
119+
const allPrs = []
120+
let cursor = null
121+
let hasNextPage = true
122+
let pageCount = 0
123+
124+
while (hasNextPage) {
125+
pageCount++
126+
core.info(`Fetching page ${pageCount} of open PRs...`)
127+
128+
const result = await withRetry(
129+
() => github.graphql(query, { owner, repo, cursor }),
130+
`GraphQL page ${pageCount}`
131+
)
132+
133+
allPrs.push(...result.repository.pullRequests.nodes)
134+
hasNextPage = result.repository.pullRequests.pageInfo.hasNextPage
135+
cursor = result.repository.pullRequests.pageInfo.endCursor
136+
137+
core.info(`Page ${pageCount}: fetched ${result.repository.pullRequests.nodes.length} PRs (total: ${allPrs.length})`)
50138
51-
stalePrs.push(pr)
139+
// Delay between pagination requests (use small batch delay for reads)
140+
if (hasNextPage) {
141+
await sleep(SMALL_BATCH_DELAY_MS)
142+
}
52143
}
53144
145+
core.info(`Found ${allPrs.length} open pull requests`)
146+
147+
const stalePrs = allPrs.filter((pr) => {
148+
const dates = [
149+
new Date(pr.createdAt),
150+
pr.commits.nodes[0] ? new Date(pr.commits.nodes[0].commit.committedDate) : null,
151+
pr.comments.nodes[0] ? new Date(pr.comments.nodes[0].createdAt) : null,
152+
pr.reviews.nodes[0] ? new Date(pr.reviews.nodes[0].createdAt) : null,
153+
].filter((d) => d !== null)
154+
155+
const lastActivity = dates.sort((a, b) => b.getTime() - a.getTime())[0]
156+
157+
if (!lastActivity || lastActivity > cutoff) {
158+
core.info(`PR #${pr.number} is fresh (last activity: ${lastActivity?.toISOString() || "unknown"})`)
159+
return false
160+
}
161+
162+
core.info(`PR #${pr.number} is STALE (last activity: ${lastActivity.toISOString()})`)
163+
return true
164+
})
165+
54166
if (!stalePrs.length) {
55167
core.info("No stale pull requests found.")
56168
return
57169
}
58170
171+
core.info(`Found ${stalePrs.length} stale pull requests`)
172+
173+
// ============================================
174+
// Close stale PRs
175+
// ============================================
176+
const requestDelayMs = stalePrs.length > SMALL_BATCH_THRESHOLD
177+
? LARGE_BATCH_DELAY_MS
178+
: SMALL_BATCH_DELAY_MS
179+
180+
core.info(`Using ${requestDelayMs}ms delay between operations (${stalePrs.length > SMALL_BATCH_THRESHOLD ? 'large' : 'small'} batch mode)`)
181+
182+
let closedCount = 0
183+
let skippedCount = 0
184+
59185
for (const pr of stalePrs) {
60186
const issue_number = pr.number
61187
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.`
62188
63189
if (dryRun) {
64-
core.info(`[dry-run] Would close PR #${issue_number} from ${pr.user.login}`)
190+
core.info(`[dry-run] Would close PR #${issue_number} from ${pr.author?.login || 'unknown'}: ${pr.title}`)
65191
continue
66192
}
67193
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}`)
194+
try {
195+
// Add comment
196+
await withRetry(
197+
() => github.rest.issues.createComment({
198+
owner,
199+
repo,
200+
issue_number,
201+
body: closeComment,
202+
}),
203+
`Comment on PR #${issue_number}`
204+
)
205+
206+
// Close PR
207+
await withRetry(
208+
() => github.rest.pulls.update({
209+
owner,
210+
repo,
211+
pull_number: issue_number,
212+
state: "closed",
213+
}),
214+
`Close PR #${issue_number}`
215+
)
216+
217+
closedCount++
218+
core.info(`Closed PR #${issue_number} from ${pr.author?.login || 'unknown'}: ${pr.title}`)
219+
220+
// Delay before processing next PR
221+
await sleep(requestDelayMs)
222+
} catch (error) {
223+
skippedCount++
224+
core.error(`Failed to close PR #${issue_number}: ${error.message}`)
225+
}
83226
}
227+
228+
const elapsed = Math.round((Date.now() - startTime) / 1000)
229+
core.info(`\n========== Summary ==========`)
230+
core.info(`Total open PRs found: ${allPrs.length}`)
231+
core.info(`Stale PRs identified: ${stalePrs.length}`)
232+
core.info(`PRs closed: ${closedCount}`)
233+
core.info(`PRs skipped (errors): ${skippedCount}`)
234+
core.info(`Elapsed time: ${elapsed}s`)
235+
core.info(`=============================`)

0 commit comments

Comments
 (0)