Skip to content

Commit 4f54224

Browse files
authored
Merge branch 'badlogic:main' into main
2 parents 4bc6856 + 83378aa commit 4f54224

File tree

130 files changed

+6807
-2046
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+6807
-2046
lines changed

.github/APPROVED_CONTRIBUTORS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,11 @@ mcollina
123123
lajarre
124124
smithbm2316
125125
drewburr
126+
gordonhwc
127+
deybhayden
128+
tintinweb
129+
asoules
130+
zhahaoyu
131+
in0vik
132+
jtac
133+
yzhg1983

.github/oss-weekend.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"active": true,
3+
"mode": "weekend",
4+
"startsAt": "2026-03-14T15:35:01.855Z",
5+
"startsAtText": "Saturday, March 14, 2026",
6+
"reopensOn": "2026-03-16",
7+
"reopensOnText": "Monday, March 16, 2026",
8+
"discordUrl": "https://discord.com/invite/3cU7Bz4UPx"
9+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
name: OSS Weekend Issues
2+
3+
on:
4+
issues:
5+
types: [opened]
6+
7+
jobs:
8+
close-issues-during-weekend:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: read
12+
issues: write
13+
steps:
14+
- name: Close new issues during OSS weekend
15+
uses: actions/github-script@v7
16+
with:
17+
script: |
18+
const issueAuthor = context.payload.issue.user.login;
19+
const defaultBranch = context.payload.repository.default_branch;
20+
21+
if (issueAuthor.endsWith('[bot]') || issueAuthor === 'dependabot[bot]') {
22+
console.log(`Skipping bot: ${issueAuthor}`);
23+
return;
24+
}
25+
26+
async function getPermission(username) {
27+
try {
28+
const { data: permissionLevel } = await github.rest.repos.getCollaboratorPermissionLevel({
29+
owner: context.repo.owner,
30+
repo: context.repo.repo,
31+
username,
32+
});
33+
return permissionLevel.permission;
34+
} catch {
35+
return null;
36+
}
37+
}
38+
39+
async function getTextFile(path) {
40+
const { data: fileContent } = await github.rest.repos.getContent({
41+
owner: context.repo.owner,
42+
repo: context.repo.repo,
43+
path,
44+
ref: defaultBranch,
45+
});
46+
47+
if (!('content' in fileContent) || typeof fileContent.content !== 'string') {
48+
throw new Error(`Expected file content for ${path}`);
49+
}
50+
51+
return Buffer.from(fileContent.content, 'base64').toString('utf8');
52+
}
53+
54+
const permission = await getPermission(issueAuthor);
55+
if (['admin', 'maintain', 'write'].includes(permission)) {
56+
console.log(`${issueAuthor} is a collaborator with ${permission} access`);
57+
return;
58+
}
59+
60+
let weekendState;
61+
try {
62+
weekendState = JSON.parse(await getTextFile('.github/oss-weekend.json'));
63+
} catch (error) {
64+
if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
65+
console.log('OSS weekend is not active');
66+
return;
67+
}
68+
throw error;
69+
}
70+
71+
if (!weekendState?.active) {
72+
console.log('OSS weekend is not active');
73+
return;
74+
}
75+
76+
const reopenDate = weekendState.reopensOnText || weekendState.reopensOn || 'after the weekend';
77+
const discordUrl = weekendState.discordUrl || 'https://discord.com/invite/3cU7Bz4UPx';
78+
const message = [
79+
`Hi @${issueAuthor}, thanks for opening an issue.`,
80+
'',
81+
`OSS weekend is active until ${reopenDate}, so new issues are being auto-closed for now.`,
82+
'',
83+
`Please reopen or submit this issue again after ${reopenDate}. For support, join [Discord](${discordUrl}).`,
84+
].join('\n');
85+
86+
await github.rest.issues.createComment({
87+
owner: context.repo.owner,
88+
repo: context.repo.repo,
89+
issue_number: context.issue.number,
90+
body: message,
91+
});
92+
93+
await github.rest.issues.update({
94+
owner: context.repo.owner,
95+
repo: context.repo.repo,
96+
issue_number: context.issue.number,
97+
state: 'closed',
98+
});

.github/workflows/pr-gate.yml

Lines changed: 77 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,47 +19,101 @@ jobs:
1919
const prAuthor = context.payload.pull_request.user.login;
2020
const defaultBranch = context.payload.repository.default_branch;
2121
22-
// Skip bots
2322
if (prAuthor.endsWith('[bot]') || prAuthor === 'dependabot[bot]') {
2423
console.log(`Skipping bot: ${prAuthor}`);
2524
return;
2625
}
2726
28-
// Check if user is a collaborator (has write access)
29-
try {
30-
const { data: permissionLevel } = await github.rest.repos.getCollaboratorPermissionLevel({
27+
async function getPermission(username) {
28+
try {
29+
const { data: permissionLevel } = await github.rest.repos.getCollaboratorPermissionLevel({
30+
owner: context.repo.owner,
31+
repo: context.repo.repo,
32+
username,
33+
});
34+
return permissionLevel.permission;
35+
} catch {
36+
return null;
37+
}
38+
}
39+
40+
async function getTextFile(path) {
41+
const { data: fileContent } = await github.rest.repos.getContent({
3142
owner: context.repo.owner,
3243
repo: context.repo.repo,
33-
username: prAuthor
44+
path,
45+
ref: defaultBranch,
3446
});
35-
if (['admin', 'write'].includes(permissionLevel.permission)) {
36-
console.log(`${prAuthor} is a collaborator with ${permissionLevel.permission} access`);
37-
return;
47+
48+
if (!('content' in fileContent) || typeof fileContent.content !== 'string') {
49+
throw new Error(`Expected file content for ${path}`);
3850
}
39-
} catch (e) {
40-
// User is not a collaborator, continue with check
51+
52+
return Buffer.from(fileContent.content, 'base64').toString('utf8');
53+
}
54+
55+
async function closePullRequest(message) {
56+
await github.rest.issues.createComment({
57+
owner: context.repo.owner,
58+
repo: context.repo.repo,
59+
issue_number: context.payload.pull_request.number,
60+
body: message,
61+
});
62+
63+
await github.rest.pulls.update({
64+
owner: context.repo.owner,
65+
repo: context.repo.repo,
66+
pull_number: context.payload.pull_request.number,
67+
state: 'closed',
68+
});
4169
}
4270
43-
// Fetch approved contributors list
44-
const { data: fileContent } = await github.rest.repos.getContent({
45-
owner: context.repo.owner,
46-
repo: context.repo.repo,
47-
path: '.github/APPROVED_CONTRIBUTORS',
48-
ref: defaultBranch
49-
});
71+
const permission = await getPermission(prAuthor);
72+
if (['admin', 'maintain', 'write'].includes(permission)) {
73+
console.log(`${prAuthor} is a collaborator with ${permission} access`);
74+
return;
75+
}
5076
51-
const content = Buffer.from(fileContent.content, 'base64').toString('utf8');
52-
const approvedList = content
77+
const approvedContent = await getTextFile('.github/APPROVED_CONTRIBUTORS');
78+
const approvedList = approvedContent
5379
.split('\n')
5480
.map(line => line.trim().toLowerCase())
5581
.filter(line => line && !line.startsWith('#'));
82+
const isApprovedContributor = approvedList.includes(prAuthor.toLowerCase());
83+
84+
let weekendState = null;
85+
try {
86+
weekendState = JSON.parse(await getTextFile('.github/oss-weekend.json'));
87+
} catch (error) {
88+
if (!(error && typeof error === 'object' && 'status' in error && error.status === 404)) {
89+
throw error;
90+
}
91+
}
92+
93+
if (weekendState?.active && isApprovedContributor) {
94+
console.log(`${prAuthor} is approved, but OSS weekend is active`);
95+
96+
const reopenDate = weekendState.reopensOnText || weekendState.reopensOn || 'after the weekend';
97+
const discordUrl = weekendState.discordUrl || 'https://discord.com/invite/3cU7Bz4UPx';
98+
const message = [
99+
`Hi @${prAuthor}, thanks for the PR.`,
100+
'',
101+
`OSS weekend is active until ${reopenDate}, so external PRs are being paused for now.`,
102+
'',
103+
'You are already on the approved contributors list, so you can resubmit this PR after the weekend without reapproval.',
104+
'',
105+
`This PR will be closed automatically. For support, join [Discord](${discordUrl}).`,
106+
].join('\n');
107+
108+
await closePullRequest(message);
109+
return;
110+
}
56111
57-
if (approvedList.includes(prAuthor.toLowerCase())) {
112+
if (isApprovedContributor) {
58113
console.log(`${prAuthor} is in the approved contributors list`);
59114
return;
60115
}
61116
62-
// Not approved - close PR with comment
63117
console.log(`${prAuthor} is not approved, closing PR`);
64118
65119
const message = [
@@ -72,19 +126,7 @@ jobs:
72126
'2. Once a maintainer approves with `lgtm`, you\'ll be added to the approved contributors list',
73127
'3. Then you can submit your PR',
74128
'',
75-
`This PR will be closed automatically. See https://github.com/${context.repo.owner}/${context.repo.repo}/blob/${defaultBranch}/CONTRIBUTING.md for more details.`
129+
`This PR will be closed automatically. See https://github.com/${context.repo.owner}/${context.repo.repo}/blob/${defaultBranch}/CONTRIBUTING.md for more details.`,
76130
].join('\n');
77131
78-
await github.rest.issues.createComment({
79-
owner: context.repo.owner,
80-
repo: context.repo.repo,
81-
issue_number: context.payload.pull_request.number,
82-
body: message
83-
});
84-
85-
await github.rest.pulls.update({
86-
owner: context.repo.owner,
87-
repo: context.repo.repo,
88-
pull_number: context.payload.pull_request.number,
89-
state: 'closed'
90-
});
132+
await closePullRequest(message);

AGENTS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ read README.md, then ask which module(s) to work on. Based on the answer, read t
2525
- NEVER run: `npm run dev`, `npm run build`, `npm test`
2626
- Only run specific tests if user instructs: `npx tsx ../../node_modules/vitest/dist/cli.js --run test/specific.test.ts`
2727
- Run tests from the package root, not the repo root.
28+
- If you create or modify a test file, you MUST run that test file and iterate until it passes.
2829
- When writing tests, run them, identify issues in either the test or implementation, and iterate until fixed.
2930
- NEVER commit unless user asks
3031

@@ -36,6 +37,13 @@ When reading issues:
3637
gh issue view <number> --json title,body,comments,labels,state
3738
```
3839

40+
## OSS Weekend
41+
- If the user says `enable OSS weekend mode until X`, run `node scripts/oss-weekend.mjs --mode=close --end-date=YYYY-MM-DD --git` with the requested end date
42+
- If the user says `end OSS weekend mode`, run `node scripts/oss-weekend.mjs --mode=open --git`
43+
- The script updates `README.md`, `packages/coding-agent/README.md`, and `.github/oss-weekend.json`
44+
- With `--git`, the script stages only those OSS weekend files, commits them, and pushes them
45+
- During OSS weekend, `.github/workflows/oss-weekend-issues.yml` auto-closes new issues from non-maintainers, and `.github/workflows/pr-gate.yml` auto-closes PRs from approved non-maintainers with the weekend message
46+
3947
When creating issues:
4048
- Add `pkg:*` labels to indicate which package(s) the issue affects
4149
- Available labels: `pkg:agent`, `pkg:ai`, `pkg:coding-agent`, `pkg:mom`, `pkg:pods`, `pkg:tui`, `pkg:web-ui`

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
<!-- OSS_WEEKEND_START -->
2+
# 🏖️ OSS Weekend
3+
4+
**Issue tracker reopens Monday, March 16, 2026.**
5+
6+
OSS weekend runs Saturday, March 14, 2026 through Monday, March 16, 2026. New issues are auto-closed during this time. For support, join [Discord](https://discord.com/invite/3cU7Bz4UPx).
7+
<!-- OSS_WEEKEND_END -->
8+
9+
---
10+
111
<p align="center">
212
<a href="https://shittycodingagent.ai">
313
<img src="https://shittycodingagent.ai/logo.svg" alt="pi logo" width="128">

0 commit comments

Comments
 (0)