mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 10:05:14 +00:00
chore: add PR approval gate for new contributors
- Add APPROVED_CONTRIBUTORS file with existing contributors - Add pr-gate.yml workflow to close PRs from unapproved contributors - Add approve-contributor.yml workflow to approve via lgtm on issues - Add CONTRIBUTING.md with guidelines - Update README.md to point to CONTRIBUTING.md
This commit is contained in:
parent
632495338f
commit
3eded2c146
5 changed files with 321 additions and 64 deletions
84
.github/APPROVED_CONTRIBUTORS
vendored
Normal file
84
.github/APPROVED_CONTRIBUTORS
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# GitHub handles of users approved to submit PRs
|
||||
# One handle per line (without @)
|
||||
# Add new contributors by commenting lgtm on their issue
|
||||
aadishv
|
||||
airtonix
|
||||
aliou
|
||||
aos
|
||||
austinm911
|
||||
banteg
|
||||
ben-vargas
|
||||
butelo
|
||||
can1357
|
||||
CarlosGtrz
|
||||
cau1k
|
||||
cmf
|
||||
crcatala
|
||||
Cursivez
|
||||
cv
|
||||
dannote
|
||||
default-anton
|
||||
dnouri
|
||||
DronNick
|
||||
enisdenjo
|
||||
ferologics
|
||||
fightbulc
|
||||
ghoulr
|
||||
gnattu
|
||||
HACKE-RC
|
||||
hewliyang
|
||||
hjanuschka
|
||||
iamd3vil
|
||||
jblwilliams
|
||||
joshp123
|
||||
jsinge97
|
||||
justram
|
||||
kaofelix
|
||||
kiliman
|
||||
kim0
|
||||
lockmeister
|
||||
LukeFost
|
||||
lukele
|
||||
m-box-mr
|
||||
marckrenn
|
||||
markusylisiurunen
|
||||
mcinteerj
|
||||
melihmucuk
|
||||
mitsuhiko
|
||||
mrexodia
|
||||
nathyong
|
||||
nickseelert
|
||||
nicobailon
|
||||
ninlds
|
||||
ogulcancelik
|
||||
patrick-kidger
|
||||
paulbettner
|
||||
Perlence
|
||||
pjtf93
|
||||
prateekmedia
|
||||
prathamdby
|
||||
ribelo
|
||||
richardgill
|
||||
robinwander
|
||||
ronyrus
|
||||
roshanasingh4
|
||||
scutifer
|
||||
skuridin
|
||||
steipete
|
||||
svkozak
|
||||
tallshort
|
||||
theBucky
|
||||
thomasmhr
|
||||
tiagoefreitas
|
||||
timolins
|
||||
tmustier
|
||||
tudoroancea
|
||||
unexge
|
||||
vaayne
|
||||
VaclavSynacek
|
||||
vsabavat
|
||||
w-winter
|
||||
Whamp
|
||||
WismutHansen
|
||||
XesGaDeus
|
||||
yevhen
|
||||
101
.github/workflows/approve-contributor.yml
vendored
Normal file
101
.github/workflows/approve-contributor.yml
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
name: Approve Contributor
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
approve:
|
||||
if: !github.event.issue.pull_request
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
|
||||
- name: Add contributor to approved list
|
||||
id: update
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const core = require('@actions/core');
|
||||
|
||||
const issueAuthor = context.payload.issue.user.login;
|
||||
const commenter = context.payload.comment.user.login;
|
||||
const commentBody = context.payload.comment.body || '';
|
||||
const approvedFile = '.github/APPROVED_CONTRIBUTORS';
|
||||
|
||||
if (!/^\s*lgtm\s*$/i.test(commentBody)) {
|
||||
console.log('Comment does not match lgtm');
|
||||
core.setOutput('status', 'skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { data: permissionLevel } = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
username: commenter
|
||||
});
|
||||
|
||||
if (!['admin', 'write'].includes(permissionLevel.permission)) {
|
||||
console.log(`${commenter} does not have write access`);
|
||||
core.setOutput('status', 'skipped');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`${commenter} does not have collaborator access`);
|
||||
core.setOutput('status', 'skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
let content = fs.readFileSync(approvedFile, 'utf8');
|
||||
const approvedList = content
|
||||
.split('\n')
|
||||
.map(line => line.trim().toLowerCase())
|
||||
.filter(line => line && !line.startsWith('#'));
|
||||
|
||||
if (approvedList.includes(issueAuthor.toLowerCase())) {
|
||||
console.log(`${issueAuthor} is already approved`);
|
||||
core.setOutput('status', 'already');
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: `@${issueAuthor} is already in the approved contributors list.`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
content = content.trimEnd() + '\n' + issueAuthor + '\n';
|
||||
fs.writeFileSync(approvedFile, content);
|
||||
|
||||
console.log(`Added ${issueAuthor} to approved contributors`);
|
||||
core.setOutput('status', 'added');
|
||||
|
||||
- name: Commit and push
|
||||
if: steps.update.outputs.status == 'added'
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add .github/APPROVED_CONTRIBUTORS
|
||||
git diff --staged --quiet || git commit -m "chore: approve contributor ${{ github.event.issue.user.login }}"
|
||||
git push
|
||||
|
||||
- name: Comment on issue
|
||||
if: steps.update.outputs.status == 'added'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issueAuthor = context.payload.issue.user.login;
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: `@${issueAuthor} has been added to the approved contributors list. You can now submit PRs. Thanks for contributing!`
|
||||
});
|
||||
88
.github/workflows/pr-gate.yml
vendored
Normal file
88
.github/workflows/pr-gate.yml
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
name: PR Gate
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
check-contributor:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check if contributor is approved
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const prAuthor = context.payload.pull_request.user.login;
|
||||
const defaultBranch = context.payload.repository.default_branch;
|
||||
|
||||
// Skip bots
|
||||
if (prAuthor.endsWith('[bot]') || prAuthor === 'dependabot[bot]') {
|
||||
console.log(`Skipping bot: ${prAuthor}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user is a collaborator (has write access)
|
||||
try {
|
||||
const { data: permissionLevel } = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
username: prAuthor
|
||||
});
|
||||
if (['admin', 'write'].includes(permissionLevel.permission)) {
|
||||
console.log(`${prAuthor} is a collaborator with ${permissionLevel.permission} access`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// User is not a collaborator, continue with check
|
||||
}
|
||||
|
||||
// Fetch approved contributors list
|
||||
const { data: fileContent } = await github.rest.repos.getContent({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: '.github/APPROVED_CONTRIBUTORS',
|
||||
ref: defaultBranch
|
||||
});
|
||||
|
||||
const content = Buffer.from(fileContent.content, 'base64').toString('utf8');
|
||||
const approvedList = content
|
||||
.split('\n')
|
||||
.map(line => line.trim().toLowerCase())
|
||||
.filter(line => line && !line.startsWith('#'));
|
||||
|
||||
if (approvedList.includes(prAuthor.toLowerCase())) {
|
||||
console.log(`${prAuthor} is in the approved contributors list`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Not approved - close PR with comment
|
||||
console.log(`${prAuthor} is not approved, closing PR`);
|
||||
|
||||
const message = `Hi @${prAuthor}, thanks for your interest in contributing!
|
||||
|
||||
We ask new contributors to open an issue first before submitting a PR. This helps us discuss the approach and avoid wasted effort.
|
||||
|
||||
**Next steps:**
|
||||
1. Open an issue describing what you want to change and why (keep it concise, write in your human voice, AI slop will be closed)
|
||||
2. Once a maintainer approves with \`lgtm\`, you'll be added to the approved contributors list
|
||||
3. Then you can submit your PR
|
||||
|
||||
This PR will be closed automatically. See https://github.com/${context.repo.owner}/${context.repo.repo}/blob/${defaultBranch}/CONTRIBUTING.md for more details.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
body: message
|
||||
});
|
||||
|
||||
await github.rest.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number,
|
||||
state: 'closed'
|
||||
});
|
||||
42
CONTRIBUTING.md
Normal file
42
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Contributing to pi
|
||||
|
||||
Thanks for wanting to contribute! This guide exists to save both of us time.
|
||||
|
||||
## The One Rule
|
||||
|
||||
**You must understand your code.** If you can't explain what your changes do and how they interact with the rest of the system, your PR will be closed.
|
||||
|
||||
Using AI to write code is fine. You can gain understanding by interrogating an agent with access to the codebase until you grasp all edge cases and effects of your changes. What's not fine is submitting agent-generated slop without that understanding.
|
||||
|
||||
If you use an agent, run it from the `pi-mono` root directory so it picks up `AGENTS.md` automatically. Your agent must follow the rules and guidelines in that file.
|
||||
|
||||
## First-Time Contributors
|
||||
|
||||
We use an approval gate for new contributors:
|
||||
|
||||
1. Open an issue describing what you want to change and why
|
||||
2. Keep it concise (if it doesn't fit on one screen, it's too long)
|
||||
3. Write in your own voice, at least for the intro
|
||||
4. A maintainer will comment `lgtm` if approved
|
||||
5. Once approved, you can submit PRs
|
||||
|
||||
This exists because AI makes it trivial to generate plausible-looking but low-quality contributions. The issue step lets us filter early.
|
||||
|
||||
## Before Submitting a PR
|
||||
|
||||
```bash
|
||||
npm run check # must pass with no errors
|
||||
./test.sh # must pass
|
||||
```
|
||||
|
||||
Do not edit `CHANGELOG.md`. Changelog entries are added by maintainers.
|
||||
|
||||
If you're adding a new provider to `packages/ai`, see `AGENTS.md` for required tests.
|
||||
|
||||
## Philosophy
|
||||
|
||||
pi's core is minimal. If your feature doesn't belong in the core, it should be an extension. PRs that bloat the core will likely be rejected.
|
||||
|
||||
## Questions?
|
||||
|
||||
Open an issue or ask on [Discord](https://discord.gg/XdVrgtQAzd).
|
||||
70
README.md
70
README.md
|
|
@ -26,80 +26,22 @@ Tools for building AI agents and managing LLM deployments.
|
|||
| **[@mariozechner/pi-web-ui](packages/web-ui)** | Web components for AI chat interfaces |
|
||||
| **[@mariozechner/pi-pods](packages/pods)** | CLI for managing vLLM deployments on GPU pods |
|
||||
|
||||
## Development
|
||||
## Contributing
|
||||
|
||||
### Setup
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines and [AGENTS.md](AGENTS.md) for project-specific rules (for both humans and agents).
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm install # Install all dependencies
|
||||
npm run build # Build all packages
|
||||
npm run check # Lint, format, and type check
|
||||
./test.sh # Run tests (skips LLM-dependent tests without API keys)
|
||||
./pi-test.sh # Run pi from sources (must be run from repo root)
|
||||
```
|
||||
|
||||
> **Note:** `npm run check` requires `npm run build` to be run first. The web-ui package uses `tsc` which needs compiled `.d.ts` files from dependencies.
|
||||
|
||||
### CI
|
||||
|
||||
GitHub Actions runs on push to `main` and on pull requests. The workflow runs `npm run check` and `npm run test` for each package in parallel.
|
||||
|
||||
**Do not add LLM API keys as secrets to this repository.** Tests that require LLM access use `describe.skipIf()` to skip when API keys are missing. This is intentional:
|
||||
|
||||
- PRs from external contributors would have access to secrets in the CI environment
|
||||
- Malicious PR code could exfiltrate API keys
|
||||
- Tests that need LLM calls are skipped on CI and run locally by developers who have keys configured
|
||||
|
||||
If you need to run LLM-dependent tests, run them locally with your own API keys.
|
||||
|
||||
### Development
|
||||
|
||||
Start watch builds for all packages:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then run with tsx:
|
||||
```bash
|
||||
cd packages/coding-agent && npx tsx src/cli.ts
|
||||
cd packages/pods && npx tsx src/cli.ts
|
||||
```
|
||||
|
||||
To run tests that don't require an LLM endpoint:
|
||||
```bash
|
||||
./test.sh
|
||||
```
|
||||
|
||||
### Versioning (Lockstep)
|
||||
|
||||
**All packages MUST always have the same version number.** Use these commands to bump versions:
|
||||
|
||||
```bash
|
||||
npm run version:patch # 0.7.5 -> 0.7.6
|
||||
npm run version:minor # 0.7.5 -> 0.8.0
|
||||
npm run version:major # 0.7.5 -> 1.0.0
|
||||
```
|
||||
|
||||
These commands:
|
||||
1. Update all package versions to the same number
|
||||
2. Update inter-package dependency versions (e.g., `pi-agent` depends on `pi-ai@^0.7.7`)
|
||||
3. Update `package-lock.json`
|
||||
|
||||
**Never manually edit version numbers.** The lockstep system ensures consistency across the monorepo.
|
||||
|
||||
### Publishing
|
||||
|
||||
```bash
|
||||
npm run release:patch # Bug fixes
|
||||
npm run release:minor # New features
|
||||
npm run release:major # Breaking changes
|
||||
```
|
||||
|
||||
This handles version bump, CHANGELOG updates, commit, tag, publish, and push.
|
||||
|
||||
**NPM Token Setup**: Requires a granular access token with "Bypass 2FA on publish" enabled.
|
||||
- Go to https://www.npmjs.com/settings/badlogic/tokens/
|
||||
- Create a new "Granular Access Token" with "Bypass 2FA on publish"
|
||||
- Set the token: `npm config set //registry.npmjs.org/:_authToken=YOUR_TOKEN`
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
Loading…
Add table
Add a link
Reference in a new issue