Skip to content

Commit ded6bb6

Browse files
committed
Merge branch 'dev' into feat/canceled-prompts-in-history
2 parents 39332f5 + ba545ba commit ded6bb6

120 files changed

Lines changed: 3161 additions & 1846 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/workflows/close-stale-prs.yml

Lines changed: 115 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,66 @@ 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"
3141
3242
core.info(`Dry run mode: ${dryRun}`)
3343
core.info(`Cutoff date: ${cutoff.toISOString()}`)
3444
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+
3581
const query = `
3682
query($owner: String!, $repo: String!, $cursor: String) {
3783
repository(owner: $owner, name: $repo) {
@@ -73,17 +119,27 @@ jobs:
73119
const allPrs = []
74120
let cursor = null
75121
let hasNextPage = true
122+
let pageCount = 0
76123
77124
while (hasNextPage) {
78-
const result = await github.graphql(query, {
79-
owner,
80-
repo,
81-
cursor,
82-
})
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+
)
83132
84133
allPrs.push(...result.repository.pullRequests.nodes)
85134
hasNextPage = result.repository.pullRequests.pageInfo.hasNextPage
86135
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+
}
87143
}
88144
89145
core.info(`Found ${allPrs.length} open pull requests`)
@@ -114,28 +170,66 @@ jobs:
114170
115171
core.info(`Found ${stalePrs.length} stale pull requests`)
116172
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+
117185
for (const pr of stalePrs) {
118186
const issue_number = pr.number
119187
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.`
120188
121189
if (dryRun) {
122-
core.info(`[dry-run] Would close PR #${issue_number} from ${pr.author.login}: ${pr.title}`)
190+
core.info(`[dry-run] Would close PR #${issue_number} from ${pr.author?.login || 'unknown'}: ${pr.title}`)
123191
continue
124192
}
125193
126-
await github.rest.issues.createComment({
127-
owner,
128-
repo,
129-
issue_number,
130-
body: closeComment,
131-
})
132-
133-
await github.rest.pulls.update({
134-
owner,
135-
repo,
136-
pull_number: issue_number,
137-
state: "closed",
138-
})
139-
140-
core.info(`Closed PR #${issue_number} from ${pr.author.login}: ${pr.title}`)
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+
}
141226
}
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(`=============================`)

.github/workflows/typecheck.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
name: typecheck
22

33
on:
4+
push:
5+
branches: [dev]
46
pull_request:
57
branches: [dev]
68
workflow_dispatch:

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
sst-env.d.ts
1+
sst-env.d.ts
2+
desktop/src/bindings.ts

AGENTS.md

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,79 +5,107 @@
55

66
## Style Guide
77

8+
### General Principles
9+
810
- Keep things in one function unless composable or reusable
9-
- Avoid unnecessary destructuring. Instead of `const { a, b } = obj`, use `obj.a` and `obj.b` to preserve context
1011
- Avoid `try`/`catch` where possible
1112
- Avoid using the `any` type
1213
- Prefer single word variable names where possible
1314
- Use Bun APIs when possible, like `Bun.file()`
1415
- Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity
1516
- Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream
1617

17-
### Avoid let statements
18+
### Naming
19+
20+
Prefer single word names for variables and functions. Only use multiple words if necessary.
21+
22+
```ts
23+
// Good
24+
const foo = 1
25+
function journal(dir: string) {}
1826

19-
We don't like `let` statements, especially combined with if/else statements.
20-
Prefer `const`.
27+
// Bad
28+
const fooBar = 1
29+
function prepareJournal(dir: string) {}
30+
```
2131

22-
Good:
32+
Reduce total variable count by inlining when a value is only used once.
2333

2434
```ts
25-
const foo = condition ? 1 : 2
35+
// Good
36+
const journal = await Bun.file(path.join(dir, "journal.json")).json()
37+
38+
// Bad
39+
const journalPath = path.join(dir, "journal.json")
40+
const journal = await Bun.file(journalPath).json()
2641
```
2742

28-
Bad:
43+
### Destructuring
44+
45+
Avoid unnecessary destructuring. Use dot notation to preserve context.
2946

3047
```ts
31-
let foo
48+
// Good
49+
obj.a
50+
obj.b
51+
52+
// Bad
53+
const { a, b } = obj
54+
```
55+
56+
### Variables
57+
58+
Prefer `const` over `let`. Use ternaries or early returns instead of reassignment.
3259

60+
```ts
61+
// Good
62+
const foo = condition ? 1 : 2
63+
64+
// Bad
65+
let foo
3366
if (condition) foo = 1
3467
else foo = 2
3568
```
3669

37-
### Avoid else statements
70+
### Control Flow
3871

39-
Prefer early returns or using an `iife` to avoid else statements.
40-
41-
Good:
72+
Avoid `else` statements. Prefer early returns.
4273

4374
```ts
75+
// Good
4476
function foo() {
4577
if (condition) return 1
4678
return 2
4779
}
48-
```
4980

50-
Bad:
51-
52-
```ts
81+
// Bad
5382
function foo() {
5483
if (condition) return 1
5584
else return 2
5685
}
5786
```
5887

59-
### Prefer single word naming
88+
### Schema Definitions (Drizzle)
6089

61-
Try your best to find a single word name for your variables, functions, etc.
62-
Only use multiple words if you cannot.
63-
64-
Good:
90+
Use snake_case for field names so column names don't need to be redefined as strings.
6591

6692
```ts
67-
const foo = 1
68-
const bar = 2
69-
const baz = 3
70-
```
71-
72-
Bad:
73-
74-
```ts
75-
const fooBar = 1
76-
const barBaz = 2
77-
const bazFoo = 3
93+
// Good
94+
const table = sqliteTable("session", {
95+
id: text().primaryKey(),
96+
project_id: text().notNull(),
97+
created_at: integer().notNull(),
98+
})
99+
100+
// Bad
101+
const table = sqliteTable("session", {
102+
id: text("id").primaryKey(),
103+
projectID: text("project_id").notNull(),
104+
createdAt: integer("created_at").notNull(),
105+
})
78106
```
79107

80108
## Testing
81109

82-
You MUST avoid using `mocks` as much as possible.
83-
Tests MUST test actual implementation, do not duplicate logic into a test.
110+
- Avoid mocks as much as possible
111+
- Test actual implementation, do not duplicate logic into tests

bun.lock

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)