Skip to content

Commit 33178e6

Browse files
committed
Merge remote-tracking branch 'upstream/dev' into feature/add-maple-ai-provider
2 parents 195a597 + b551195 commit 33178e6

548 files changed

Lines changed: 47056 additions & 16112 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/actions/setup-bun/action.yml

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@ 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
8+
with:
9+
key: ${{ github.repository }}-bun-cache
10+
path: ~/.bun
11+
612
- name: Setup Bun
713
uses: oven-sh/setup-bun@v2
814
with:
915
bun-version-file: package.json
1016

11-
- name: Cache ~/.bun
12-
id: cache-bun
13-
uses: actions/cache@v4
14-
with:
15-
path: ~/.bun
16-
key: ${{ runner.os }}-bun-${{ hashFiles('package.json') }}-${{ hashFiles('bun.lockb', 'bun.lock') }}
17-
restore-keys: |
18-
${{ runner.os }}-bun-${{ hashFiles('package.json') }}-
19-
2017
- name: Install dependencies
2118
run: bun install
2219
shell: bash
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: "Setup Git Committer"
2+
description: "Create app token and configure git user"
3+
inputs:
4+
opencode-app-id:
5+
description: "OpenCode GitHub App ID"
6+
required: true
7+
opencode-app-secret:
8+
description: "OpenCode GitHub App private key"
9+
required: true
10+
outputs:
11+
token:
12+
description: "GitHub App token"
13+
value: ${{ steps.apptoken.outputs.token }}
14+
app-slug:
15+
description: "GitHub App slug"
16+
value: ${{ steps.apptoken.outputs.app-slug }}
17+
runs:
18+
using: "composite"
19+
steps:
20+
- name: Create app token
21+
id: apptoken
22+
uses: actions/create-github-app-token@v2
23+
with:
24+
app-id: ${{ inputs.opencode-app-id }}
25+
private-key: ${{ inputs.opencode-app-secret }}
26+
owner: ${{ github.repository_owner }}
27+
28+
- name: Configure git user
29+
run: |
30+
slug="${{ steps.apptoken.outputs.app-slug }}"
31+
git config --global user.name "${slug}[bot]"
32+
git config --global user.email "${slug}[bot]@users.noreply.github.com"
33+
shell: bash
34+
35+
- name: Clear checkout auth
36+
run: |
37+
git config --local --unset-all http.https://github.com/.extraheader || true
38+
shell: bash
39+
40+
- name: Configure git remote
41+
run: |
42+
git remote set-url origin https://x-access-token:${{ steps.apptoken.outputs.token }}@github.com/${{ github.repository }}
43+
shell: bash

.github/pull_request_template.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
### What does this PR do?
22

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.
4+
5+
**If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!**
6+
37
### How did you verify your code works?

.github/workflows/beta.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: beta
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: "0 * * * *"
7+
8+
jobs:
9+
sync:
10+
runs-on: blacksmith-4vcpu-ubuntu-2404
11+
permissions:
12+
contents: write
13+
pull-requests: write
14+
steps:
15+
- name: Checkout repository
16+
uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
20+
- name: Setup Bun
21+
uses: ./.github/actions/setup-bun
22+
23+
- name: Setup Git Committer
24+
id: setup-git-committer
25+
uses: ./.github/actions/setup-git-committer
26+
with:
27+
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
28+
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
29+
30+
- name: Sync beta branch
31+
env:
32+
GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
33+
run: bun script/beta.ts
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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+
timeout-minutes: 15
22+
steps:
23+
- name: Close inactive PRs
24+
uses: actions/github-script@v8
25+
with:
26+
github-token: ${{ secrets.GITHUB_TOKEN }}
27+
script: |
28+
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()
38+
const cutoff = new Date(Date.now() - DAYS_INACTIVE * 24 * 60 * 60 * 1000)
39+
const { owner, repo } = context.repo
40+
const dryRun = context.payload.inputs?.dryRun === "true"
41+
42+
core.info(`Dry run mode: ${dryRun}`)
43+
core.info(`Cutoff date: ${cutoff.toISOString()}`)
44+
45+
function sleep(ms) {
46+
return new Promise(resolve => setTimeout(resolve, ms))
47+
}
48+
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+
}
76+
}
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})`)
138+
139+
// Delay between pagination requests (use small batch delay for reads)
140+
if (hasNextPage) {
141+
await sleep(SMALL_BATCH_DELAY_MS)
142+
}
143+
}
144+
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+
166+
if (!stalePrs.length) {
167+
core.info("No stale pull requests found.")
168+
return
169+
}
170+
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+
185+
for (const pr of stalePrs) {
186+
const issue_number = pr.number
187+
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.`
188+
189+
if (dryRun) {
190+
core.info(`[dry-run] Would close PR #${issue_number} from ${pr.author?.login || 'unknown'}: ${pr.title}`)
191+
continue
192+
}
193+
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+
}
226+
}
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)