Skip to content

Commit 5012196

Browse files
committed
Thêm webhook Gemini review PR
1 parent 8cf0e8d commit 5012196

9 files changed

Lines changed: 918 additions & 9 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,16 @@ jobs:
160160
MERGEOS_GITHUB_OAUTH_CLIENT_SECRET: ${{ secrets.MERGEOS_GITHUB_OAUTH_CLIENT_SECRET }}
161161
MERGEOS_GOOGLE_CLIENT_ID: ${{ secrets.MERGEOS_GOOGLE_CLIENT_ID }}
162162
MERGEOS_GOOGLE_CLIENT_SECRET: ${{ secrets.MERGEOS_GOOGLE_CLIENT_SECRET }}
163+
GEMINI_API_KEYS: ${{ secrets.GEMINI_API_KEYS }}
164+
GEMINI_REVIEW_MODEL: ${{ secrets.GEMINI_REVIEW_MODEL }}
165+
GEMINI_REVIEW_WEBHOOK_SECRET: ${{ secrets.GEMINI_REVIEW_WEBHOOK_SECRET }}
166+
GEMINI_REVIEW_MAX_PATCH_BYTES: ${{ secrets.GEMINI_REVIEW_MAX_PATCH_BYTES }}
163167
with:
164168
host: ${{ secrets.DEPLOY_HOST }}
165169
username: ${{ secrets.DEPLOY_USER }}
166170
password: ${{ secrets.DEPLOY_PASSWORD }}
167171
port: ${{ secrets.DEPLOY_PORT }}
168-
envs: DEPLOY_PATH,ADMIN_EMAIL,ADMIN_PASSWORD,DEV_PAYMENT_ENABLED,DEV_PAYMENT_CODE,MERGEOS_GITHUB_TOKEN,GITHUB_OWNER,GITHUB_OWNER_TYPE,MERGEOS_GITHUB_APP_ID,MERGEOS_GITHUB_APP_CLIENT_ID,MERGEOS_GITHUB_APP_CLIENT_SECRET,MERGEOS_GITHUB_OAUTH_CLIENT_ID,MERGEOS_GITHUB_OAUTH_CLIENT_SECRET,MERGEOS_GOOGLE_CLIENT_ID,MERGEOS_GOOGLE_CLIENT_SECRET
172+
envs: DEPLOY_PATH,ADMIN_EMAIL,ADMIN_PASSWORD,DEV_PAYMENT_ENABLED,DEV_PAYMENT_CODE,MERGEOS_GITHUB_TOKEN,GITHUB_OWNER,GITHUB_OWNER_TYPE,MERGEOS_GITHUB_APP_ID,MERGEOS_GITHUB_APP_CLIENT_ID,MERGEOS_GITHUB_APP_CLIENT_SECRET,MERGEOS_GITHUB_OAUTH_CLIENT_ID,MERGEOS_GITHUB_OAUTH_CLIENT_SECRET,MERGEOS_GOOGLE_CLIENT_ID,MERGEOS_GOOGLE_CLIENT_SECRET,GEMINI_API_KEYS,GEMINI_REVIEW_MODEL,GEMINI_REVIEW_WEBHOOK_SECRET,GEMINI_REVIEW_MAX_PATCH_BYTES
169173
script: |
170174
set -e
171175
APP_DIR="${DEPLOY_PATH:-$HOME/mergeos}"
@@ -263,6 +267,10 @@ jobs:
263267
Environment=GITHUB_OAUTH_CLIENT_SECRET=$MERGEOS_GITHUB_OAUTH_CLIENT_SECRET
264268
Environment=GOOGLE_CLIENT_ID=$MERGEOS_GOOGLE_CLIENT_ID
265269
Environment=GOOGLE_CLIENT_SECRET=$MERGEOS_GOOGLE_CLIENT_SECRET
270+
Environment=GEMINI_API_KEYS=$GEMINI_API_KEYS
271+
Environment=GEMINI_REVIEW_MODEL=$GEMINI_REVIEW_MODEL
272+
Environment=GEMINI_REVIEW_WEBHOOK_SECRET=$GEMINI_REVIEW_WEBHOOK_SECRET
273+
Environment=GEMINI_REVIEW_MAX_PATCH_BYTES=$GEMINI_REVIEW_MAX_PATCH_BYTES
266274
ExecStart=$APP_DIR/mergeos
267275
Restart=always
268276
RestartSec=5
@@ -276,7 +284,7 @@ jobs:
276284
systemctl --user is-active --quiet mergeos.service
277285
else
278286
pkill -f "$APP_DIR/mergeos" || true
279-
MERGEOS_ENV=production PORT=8080 DATABASE_URL="$DATABASE_URL_VALUE" ADMIN_EMAIL="$ADMIN_EMAIL" ADMIN_PASSWORD="$ADMIN_PASSWORD" DEV_PAYMENT_ENABLED="$DEV_PAYMENT_ENABLED" DEV_PAYMENT_CODE="$DEV_PAYMENT_CODE" GITHUB_TOKEN="$MERGEOS_GITHUB_TOKEN" GITHUB_OWNER="$GITHUB_OWNER" GITHUB_OWNER_TYPE="$GITHUB_OWNER_TYPE" GITHUB_APP_ID="$MERGEOS_GITHUB_APP_ID" GITHUB_APP_CLIENT_ID="$MERGEOS_GITHUB_APP_CLIENT_ID" GITHUB_APP_CLIENT_SECRET="$MERGEOS_GITHUB_APP_CLIENT_SECRET" GITHUB_OAUTH_CLIENT_ID="$MERGEOS_GITHUB_OAUTH_CLIENT_ID" GITHUB_OAUTH_CLIENT_SECRET="$MERGEOS_GITHUB_OAUTH_CLIENT_SECRET" GOOGLE_CLIENT_ID="$MERGEOS_GOOGLE_CLIENT_ID" GOOGLE_CLIENT_SECRET="$MERGEOS_GOOGLE_CLIENT_SECRET" nohup "$APP_DIR/mergeos" > "$APP_DIR/mergeos.log" 2> "$APP_DIR/mergeos.err.log" &
287+
MERGEOS_ENV=production PORT=8080 DATABASE_URL="$DATABASE_URL_VALUE" ADMIN_EMAIL="$ADMIN_EMAIL" ADMIN_PASSWORD="$ADMIN_PASSWORD" DEV_PAYMENT_ENABLED="$DEV_PAYMENT_ENABLED" DEV_PAYMENT_CODE="$DEV_PAYMENT_CODE" GITHUB_TOKEN="$MERGEOS_GITHUB_TOKEN" GITHUB_OWNER="$GITHUB_OWNER" GITHUB_OWNER_TYPE="$GITHUB_OWNER_TYPE" GITHUB_APP_ID="$MERGEOS_GITHUB_APP_ID" GITHUB_APP_CLIENT_ID="$MERGEOS_GITHUB_APP_CLIENT_ID" GITHUB_APP_CLIENT_SECRET="$MERGEOS_GITHUB_APP_CLIENT_SECRET" GITHUB_OAUTH_CLIENT_ID="$MERGEOS_GITHUB_OAUTH_CLIENT_ID" GITHUB_OAUTH_CLIENT_SECRET="$MERGEOS_GITHUB_OAUTH_CLIENT_SECRET" GOOGLE_CLIENT_ID="$MERGEOS_GOOGLE_CLIENT_ID" GOOGLE_CLIENT_SECRET="$MERGEOS_GOOGLE_CLIENT_SECRET" GEMINI_API_KEYS="$GEMINI_API_KEYS" GEMINI_REVIEW_MODEL="$GEMINI_REVIEW_MODEL" GEMINI_REVIEW_WEBHOOK_SECRET="$GEMINI_REVIEW_WEBHOOK_SECRET" GEMINI_REVIEW_MAX_PATCH_BYTES="$GEMINI_REVIEW_MAX_PATCH_BYTES" nohup "$APP_DIR/mergeos" > "$APP_DIR/mergeos.log" 2> "$APP_DIR/mergeos.err.log" &
280288
fi
281289
if command -v sudo >/dev/null 2>&1; then
282290
NODE_MAJOR=22

BOUNTY-POLICY.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Every bounty PR must include:
4545
- Test commands and results.
4646
- Notes for migrations, environment variables, risk, or deployment changes.
4747

48-
Maintainers use these labels while reviewing bounty PRs. GitHub Copilot code review should flag missing readiness items and suggest the relevant labels in its review summary, but maintainers apply the labels manually:
48+
Maintainers use these labels while reviewing bounty PRs. The `Gemini PR review` webhook sends new and updated PRs to the MergeOS reviewer service. That service checks repository star status, evidence, tests, bounty context, and code risk, then comments on the PR with the readiness summary:
4949

5050
- `evidence: missing`
5151
- `evidence: provided`
@@ -54,6 +54,8 @@ Maintainers use these labels while reviewing bounty PRs. GitHub Copilot code rev
5454
- `bounty: bug`
5555
- `bounty: feature`
5656

57+
GitHub Copilot code review can still flag code-quality and readiness issues when quota is available, but bounty readiness must not depend on Copilot being available.
58+
5759
## Payout Rule
5860

5961
MRG token rewards are only eligible after:

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@ Important backend variables:
274274
- `CRYPTO_RPC_URL`, `CRYPTO_RECEIVER`, `CRYPTO_ASSET`, `CRYPTO_TOKEN_CONTRACT`: crypto verifier
275275
- `GITHUB_TOKEN`, `GITHUB_OWNER`, `GITHUB_OWNER_TYPE`: backend runtime values for GitHub bounty repo creation and admin PR merge actions
276276
- `MERGEOS_GITHUB_TOKEN`: Docker Compose and GitHub Actions secret name that maps into backend `GITHUB_TOKEN`; use a personal access token with repo write access, not the automatic GitHub Actions token
277+
- `GEMINI_API_KEYS`: comma-separated Gemini API key pool for automated PR review round-robin
278+
- `GEMINI_REVIEW_MODEL`: Gemini reviewer model, default `gemini-2.5-flash`
279+
- `GEMINI_REVIEW_WEBHOOK_SECRET`: GitHub webhook secret used to verify `X-Hub-Signature-256`
280+
- `GEMINI_REVIEW_MAX_PATCH_BYTES`: max patch context sent to Gemini, default `70000`
277281
- `GITHUB_APP_ID`, `GITHUB_APP_CLIENT_ID`, `GITHUB_APP_CLIENT_SECRET`: backend runtime values for GitHub App user authorization, login, and MRG wallet linking
278282
- `MERGEOS_GITHUB_APP_ID`, `MERGEOS_GITHUB_APP_CLIENT_ID`, `MERGEOS_GITHUB_APP_CLIENT_SECRET`: Docker Compose and GitHub Actions secret names that map into the backend runtime values
279283
- `GITHUB_OAUTH_CLIENT_ID`, `GITHUB_OAUTH_CLIENT_SECRET`: legacy backend aliases still accepted for older OAuth configuration
@@ -292,6 +296,7 @@ Public:
292296
- `GET /api/public/ledger`
293297
- `GET /api/public/marketplace`
294298
- `POST /api/public/repo/issues`
299+
- `POST /api/integrations/github/pr-review` GitHub webhook receiver for Gemini PR review. Configure GitHub Webhooks with Payload URL `https://uta.mergeos.shop/api/integrations/github/pr-review`, Content type `application/json`, the same secret as `GEMINI_REVIEW_WEBHOOK_SECRET`, and events `Pull requests` plus `Issue comments`.
295300

296301
Auth:
297302

backend/.env.local.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ CRYPTO_MIN_CONFIRMATIONS=1
3838
GITHUB_TOKEN=
3939
GITHUB_OWNER=mergeos-bounties
4040
GITHUB_OWNER_TYPE=org
41+
GEMINI_API_KEYS=
42+
GEMINI_REVIEW_MODEL=gemini-2.5-flash
43+
GEMINI_REVIEW_WEBHOOK_SECRET=
44+
GEMINI_REVIEW_MAX_PATCH_BYTES=70000
4145
GITHUB_APP_ID=
4246
GITHUB_APP_CLIENT_ID=
4347
GITHUB_APP_CLIENT_SECRET=

backend/.env.production.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ CRYPTO_MIN_CONFIRMATIONS=3
3838
GITHUB_TOKEN=
3939
GITHUB_OWNER=mergeos-bounties
4040
GITHUB_OWNER_TYPE=org
41+
GEMINI_API_KEYS=
42+
GEMINI_REVIEW_MODEL=gemini-2.5-flash
43+
GEMINI_REVIEW_WEBHOOK_SECRET=
44+
GEMINI_REVIEW_MAX_PATCH_BYTES=70000
4145
GITHUB_APP_ID=
4246
GITHUB_APP_CLIENT_ID=
4347
GITHUB_APP_CLIENT_SECRET=

backend/internal/core/config.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ type Config struct {
5555
GitHubOwner string
5656
GitHubOwnerType string
5757

58+
GeminiAPIKeys []string
59+
GeminiReviewModel string
60+
GeminiReviewWebhookSecret string
61+
GeminiReviewMaxPatchBytes int64
62+
5863
GitHubAppID string
5964
GitHubOAuthClientID string
6065
GitHubOAuthClientSecret string
@@ -146,10 +151,20 @@ func LoadConfig() Config {
146151
CryptoWeiPerUSDCent: os.Getenv("CRYPTO_WEI_PER_USD_CENT"),
147152
CryptoMinConfirmations: getenvInt64("CRYPTO_MIN_CONFIRMATIONS", 1),
148153

149-
GitHubToken: os.Getenv("GITHUB_TOKEN"),
154+
GitHubToken: firstEnv("GITHUB_TOKEN", "MERGEOS_GITHUB_TOKEN"),
150155
GitHubOwner: getenv("GITHUB_OWNER", defaultGitHubOwner),
151156
GitHubOwnerType: strings.ToLower(getenv("GITHUB_OWNER_TYPE", "org")),
152157

158+
GeminiAPIKeys: splitEnvList(firstEnv(
159+
"GEMINI_API_KEYS",
160+
"MERGEOS_GEMINI_API_KEYS",
161+
"GEMINI_API_KEY",
162+
"MERGEOS_GEMINI_API_KEY",
163+
)),
164+
GeminiReviewModel: getenv("GEMINI_REVIEW_MODEL", "gemini-2.5-flash"),
165+
GeminiReviewWebhookSecret: firstEnv("GEMINI_REVIEW_WEBHOOK_SECRET", "MERGEOS_GEMINI_REVIEW_WEBHOOK_SECRET"),
166+
GeminiReviewMaxPatchBytes: getenvInt64("GEMINI_REVIEW_MAX_PATCH_BYTES", 70000),
167+
153168
GitHubAppID: firstEnv("GITHUB_APP_ID", "MERGEOS_GITHUB_APP_ID"),
154169
GitHubOAuthClientID: githubOAuthClientID,
155170
GitHubOAuthClientSecret: githubOAuthClientSecret,
@@ -236,6 +251,10 @@ func (c Config) GitHubReady() bool {
236251
return c.GitHubToken != "" && c.GitHubOwner != ""
237252
}
238253

254+
func (c Config) GeminiReviewReady() bool {
255+
return len(c.GeminiAPIKeys) > 0 && c.GitHubToken != "" && c.GeminiReviewWebhookSecret != ""
256+
}
257+
239258
func (c Config) GitHubOAuthReady() bool {
240259
return c.GitHubOAuthClientID != "" && c.GitHubOAuthClientSecret != ""
241260
}
@@ -262,6 +281,20 @@ func firstEnv(keys ...string) string {
262281
return ""
263282
}
264283

284+
func splitEnvList(value string) []string {
285+
parts := strings.FieldsFunc(value, func(r rune) bool {
286+
return r == ',' || r == ';' || r == '\n' || r == '\r'
287+
})
288+
result := []string{}
289+
for _, part := range parts {
290+
part = strings.TrimSpace(part)
291+
if part != "" {
292+
result = append(result, part)
293+
}
294+
}
295+
return result
296+
}
297+
265298
func getenvBool(key string, fallback bool) bool {
266299
value := strings.TrimSpace(os.Getenv(key))
267300
if value == "" {

0 commit comments

Comments
 (0)