From 651d6850a1ef9b53c68245e2b0b9df752ea0c6ac Mon Sep 17 00:00:00 2001 From: Timothy Date: Wed, 18 Mar 2026 14:49:21 -0700 Subject: [PATCH] fix: bounty tracker change --- .github/workflows/bounty-completed.yml | 18 +++- .github/workflows/link-discord.yml | 126 ----------------------- .github/workflows/weekly-leaderboard.yml | 4 + contributors.yml | 27 ----- docs/bounty-program/README.md | 8 +- docs/bounty-program/contributor-guide.md | 6 +- docs/bounty-program/setup-guide.md | 5 +- scripts/bounty-tracker.ts | 80 ++++++-------- 8 files changed, 57 insertions(+), 217 deletions(-) delete mode 100644 .github/workflows/link-discord.yml delete mode 100644 contributors.yml diff --git a/.github/workflows/bounty-completed.yml b/.github/workflows/bounty-completed.yml index 0097558f..600c25b6 100644 --- a/.github/workflows/bounty-completed.yml +++ b/.github/workflows/bounty-completed.yml @@ -5,11 +5,19 @@ on: pull_request_target: types: [closed] + workflow_dispatch: + inputs: + pr_number: + description: "PR number to process (for missed bounties)" + required: true + type: number + jobs: bounty-notify: if: > - github.event.pull_request.merged == true && - contains(join(github.event.pull_request.labels.*.name, ','), 'bounty:') + github.event_name == 'workflow_dispatch' || + (github.event.pull_request.merged == true && + contains(join(github.event.pull_request.labels.*.name, ','), 'bounty:')) runs-on: ubuntu-latest timeout-minutes: 5 permissions: @@ -25,6 +33,9 @@ jobs: with: bun-version: latest + - name: Install mongodb dependency + run: bun add mongodb + - name: Award XP and notify Discord run: bun run scripts/bounty-tracker.ts notify env: @@ -32,6 +43,7 @@ jobs: GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_BOUNTY_WEBHOOK_URL }} + MONGODB_URI: ${{ secrets.MONGODB_URI }} LURKR_API_KEY: ${{ secrets.LURKR_API_KEY }} LURKR_GUILD_ID: ${{ secrets.LURKR_GUILD_ID }} - PR_NUMBER: ${{ github.event.pull_request.number }} + PR_NUMBER: ${{ inputs.pr_number || github.event.pull_request.number }} diff --git a/.github/workflows/link-discord.yml b/.github/workflows/link-discord.yml deleted file mode 100644 index 4ce1fdb6..00000000 --- a/.github/workflows/link-discord.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: Link Discord account -description: Auto-creates a PR to add contributor to contributors.yml when a link-discord issue is opened - -on: - issues: - types: [opened] - -jobs: - link-discord: - if: contains(github.event.issue.labels.*.name, 'link-discord') - runs-on: ubuntu-latest - timeout-minutes: 2 - permissions: - contents: write - issues: write - pull-requests: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Parse issue and update contributors.yml - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - - const issue = context.payload.issue; - const githubUsername = issue.user.login; - - // Parse the issue body for form fields - const body = issue.body || ''; - - // Extract Discord ID — look for the numeric value after the "Discord User ID" heading - const discordMatch = body.match(/### Discord User ID\s*\n\s*(\d{17,20})/); - if (!discordMatch) { - await github.rest.issues.createComment({ - ...context.repo, - issue_number: issue.number, - body: `Could not find a valid Discord ID in the issue body. Please make sure you entered a numeric ID (17-20 digits), not a username.\n\nExample: \`123456789012345678\`` - }); - await github.rest.issues.update({ - ...context.repo, - issue_number: issue.number, - state: 'closed', - state_reason: 'not_planned' - }); - return; - } - const discordId = discordMatch[1]; - - // Extract display name (optional) - const nameMatch = body.match(/### Display Name \(optional\)\s*\n\s*(.+)/); - const displayName = nameMatch ? nameMatch[1].trim() : ''; - - // Check if user already exists - const yml = fs.readFileSync('contributors.yml', 'utf-8'); - if (yml.includes(`github: ${githubUsername}`)) { - await github.rest.issues.createComment({ - ...context.repo, - issue_number: issue.number, - body: `@${githubUsername} is already in \`contributors.yml\`. If you need to update your Discord ID, please edit the file directly via PR.` - }); - await github.rest.issues.update({ - ...context.repo, - issue_number: issue.number, - state: 'closed', - state_reason: 'completed' - }); - return; - } - - // Append entry to contributors.yml - let entry = ` - github: ${githubUsername}\n discord: "${discordId}"`; - if (displayName && displayName !== '_No response_') { - entry += `\n name: ${displayName}`; - } - entry += '\n'; - - const updated = yml.trimEnd() + '\n' + entry; - fs.writeFileSync('contributors.yml', updated); - - // Set outputs for commit step - core.exportVariable('GITHUB_USERNAME', githubUsername); - core.exportVariable('DISCORD_ID', discordId); - core.exportVariable('ISSUE_NUMBER', issue.number.toString()); - - - name: Create PR - run: | - # Check if there are changes - if git diff --quiet contributors.yml; then - echo "No changes to contributors.yml" - exit 0 - fi - - BRANCH="docs/link-discord-${GITHUB_USERNAME}" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git checkout -b "$BRANCH" - git add contributors.yml - git commit -m "docs: link @${GITHUB_USERNAME} to Discord" - git push origin "$BRANCH" - - gh pr create \ - --title "docs: link @${GITHUB_USERNAME} to Discord" \ - --body "Adds @${GITHUB_USERNAME} (Discord \`${DISCORD_ID}\`) to \`contributors.yml\` for bounty XP tracking. - - Closes #${ISSUE_NUMBER}" \ - --base main \ - --head "$BRANCH" \ - --label "link-discord" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Notify on issue - uses: actions/github-script@v7 - with: - script: | - const username = process.env.GITHUB_USERNAME; - const issueNumber = parseInt(process.env.ISSUE_NUMBER); - - await github.rest.issues.createComment({ - ...context.repo, - issue_number: issueNumber, - body: `A PR has been created to link your account. A maintainer will merge it shortly — once merged, you'll receive XP and Discord pings when your bounty PRs are merged.` - }); diff --git a/.github/workflows/weekly-leaderboard.yml b/.github/workflows/weekly-leaderboard.yml index 6bf730df..389a52ad 100644 --- a/.github/workflows/weekly-leaderboard.yml +++ b/.github/workflows/weekly-leaderboard.yml @@ -28,6 +28,9 @@ jobs: with: bun-version: latest + - name: Install mongodb dependency + run: bun add mongodb + - name: Post leaderboard to Discord run: bun run scripts/bounty-tracker.ts leaderboard env: @@ -35,6 +38,7 @@ jobs: GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_BOUNTY_WEBHOOK_URL }} + MONGODB_URI: ${{ secrets.MONGODB_URI }} LURKR_API_KEY: ${{ secrets.LURKR_API_KEY }} LURKR_GUILD_ID: ${{ secrets.LURKR_GUILD_ID }} SINCE_DATE: ${{ github.event.inputs.since_date || '' }} diff --git a/contributors.yml b/contributors.yml deleted file mode 100644 index 7bb38fa9..00000000 --- a/contributors.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Identity mapping: GitHub username -> Discord ID -# -# This file links GitHub accounts to Discord accounts for the -# Integration Bounty Program. When a bounty PR is merged, the -# GitHub Action uses this file to ping the contributor on Discord. -# -# HOW TO ADD YOURSELF: -# Open a "Link Discord Account" issue: -# https://github.com/aden-hive/hive/issues/new?template=link-discord.yml -# A GitHub Action will automatically add your entry here. -# -# To find your Discord ID: -# 1. Open Discord Settings > Advanced > Enable Developer Mode -# 2. Right-click your name > Copy User ID -# -# Format: -# - github: your-github-username -# discord: "your-discord-id" # quotes required (it's a number) -# name: Your Display Name # optional - -contributors: - # - github: example-user - # discord: "123456789012345678" - # name: Example User - - github: TimothyZhang7 - discord: "408460790061072384" - name: Timothy@Aden diff --git a/docs/bounty-program/README.md b/docs/bounty-program/README.md index f944150c..1272739b 100644 --- a/docs/bounty-program/README.md +++ b/docs/bounty-program/README.md @@ -157,7 +157,7 @@ All bounty types open in parallel. Contributors self-select. Daily progress upda PR merged with bounty:* label → GitHub Action runs bounty-tracker.ts → Calculates points from label - → Resolves GitHub → Discord ID via contributors.yml + → Resolves GitHub → Discord ID via MongoDB (hive.contributors) → Pushes XP to Lurkr API → Posts notification to #integrations-announcements ``` @@ -166,7 +166,7 @@ See the [Setup Guide](setup-guide.md) for full configuration (Lurkr, webhooks, s ### Identity Linking -Contributors link GitHub ↔ Discord by opening a [Link Discord Account](https://github.com/aden-hive/hive/issues/new?template=link-discord.yml) issue. A GitHub Action auto-adds them to `contributors.yml` and closes the issue. +Contributors link GitHub ↔ Discord by running `/link-github` in Discord. The bot verifies ownership via a public gist, then stores the mapping in MongoDB. Without this link, bounties are still tracked but Lurkr can't push XP to your Discord account. @@ -181,7 +181,7 @@ Without this link, bounties are still tracked but Lurkr can't push XP to your Di | Agent Builder role | Lurkr bot | Auto-assigned at level 5 | | OSS Contributor role | Lurkr bot | Auto-assigned at level 15 | | Core Contributor role | Maintainer | Manual (involves money) | -| Identity linking | contributors.yml | PR-based, reviewed by maintainers | +| Identity linking | Discord bot → MongoDB | `/link-github` command with gist verification | ## Guides @@ -203,4 +203,4 @@ Without this link, bounties are still tracked but Lurkr can't push XP to your Di - `.github/workflows/weekly-leaderboard.yml` — Monday leaderboard post - `scripts/bounty-tracker.ts` — Point calculation, Lurkr API, Discord formatting - `scripts/setup-bounty-labels.sh` — One-time label setup -- `contributors.yml` — GitHub ↔ Discord identity mapping +- MongoDB `hive.contributors` — GitHub ↔ Discord identity mapping (managed by Discord bot) diff --git a/docs/bounty-program/contributor-guide.md b/docs/bounty-program/contributor-guide.md index 9bcc1a23..bf5147d9 100644 --- a/docs/bounty-program/contributor-guide.md +++ b/docs/bounty-program/contributor-guide.md @@ -6,9 +6,7 @@ Earn XP, Discord roles, and eventually real money by contributing to the Aden ag ### 1. Link your GitHub and Discord -Open a [Link Discord Account](https://github.com/aden-hive/hive/issues/new?template=link-discord.yml) issue — just paste your Discord ID and submit. A GitHub Action will automatically add you to `contributors.yml` and close the issue. - -To find your Discord ID: Discord Settings > Advanced > Enable **Developer Mode**, then right-click your name > **Copy User ID**. +Run `/link-github your-github-username` in Discord. The bot will give you a verification code — create a public gist with that code, then run `/verify`. Done. Without this link, Lurkr can't push XP to your Discord account. @@ -154,7 +152,7 @@ A: Yes. Most services have free tiers. The bounty issue links to where you get t A: Contribute consistently across different bounty types for 4+ weeks. Maintainers will nominate you. **Q: What if I haven't linked my Discord yet?** -A: You'll still get credit in GitHub, but no Lurkr XP or Discord roles. Add yourself to `contributors.yml`. +A: You'll still get credit in GitHub, but no Lurkr XP or Discord roles. Run `/link-github` in Discord. ## Quick Reference diff --git a/docs/bounty-program/setup-guide.md b/docs/bounty-program/setup-guide.md index 59b83c9b..4e2a984b 100644 --- a/docs/bounty-program/setup-guide.md +++ b/docs/bounty-program/setup-guide.md @@ -104,6 +104,7 @@ Repo Settings > Secrets and variables > Actions: | `DISCORD_BOUNTY_WEBHOOK_URL` | Webhook URL from Step 5 | | `LURKR_API_KEY` | Lurkr API key from Step 4f | | `LURKR_GUILD_ID` | Your Discord server ID\* | +| `MONGODB_URI` | MongoDB connection string | \*Enable Developer Mode in Discord, right-click server name > Copy Server ID. @@ -146,12 +147,12 @@ powerbi, redis - [ ] All 3 GitHub secrets added - [ ] Both workflows enabled (`bounty-completed.yml`, `weekly-leaderboard.yml`) - [ ] Test PR + merge triggers Discord notification -- [ ] `contributors.yml` exists at repo root +- [ ] MongoDB `hive.contributors` collection accessible ## Troubleshooting **No Discord message:** Check `DISCORD_BOUNTY_WEBHOOK_URL` secret and Action logs. -**Lurkr XP not awarded:** Confirm API key is Read/Write, contributor is in `contributors.yml`, check Action logs for `Lurkr XP push failed`. +**Lurkr XP not awarded:** Confirm API key is Read/Write, contributor has run `/link-github` in Discord, check Action logs for `Lurkr XP push failed`. **Role not assigned:** Verify role rewards in the Lurkr dashboard or via `/config set`. Lurkr's role must be above the roles it assigns in server hierarchy. diff --git a/scripts/bounty-tracker.ts b/scripts/bounty-tracker.ts index b2dd06ab..c64db1cb 100644 --- a/scripts/bounty-tracker.ts +++ b/scripts/bounty-tracker.ts @@ -12,13 +12,13 @@ * GITHUB_REPOSITORY_OWNER — e.g. "adenhq" * GITHUB_REPOSITORY_NAME — e.g. "hive" * DISCORD_WEBHOOK_URL — Discord webhook for #integrations-announcements + * MONGODB_URI — MongoDB connection string (contributors collection) * LURKR_API_KEY — Lurkr Read/Write API key (for XP push) * LURKR_GUILD_ID — Discord server ID where Lurkr is installed * PR_NUMBER — (notify mode) The merged PR number */ -import { readFileSync } from "fs"; -import { join } from "path"; +import { MongoClient } from "mongodb"; // --------------------------------------------------------------------------- // Types @@ -151,59 +151,37 @@ async function getMergedBountyPRs( } // --------------------------------------------------------------------------- -// Identity resolution +// Identity resolution (MongoDB-backed) // --------------------------------------------------------------------------- -// Parse contributors.yml without a YAML dependency. -// The format is simple enough to parse with regex: -// contributors: -// - github: jane-doe -// discord: "123456789012345678" -// name: Jane Doe -function parseContributorsYaml(raw: string): Contributor[] { - const contributors: Contributor[] = []; - let current: Partial | null = null; - - for (const line of raw.split("\n")) { - const trimmed = line.trim(); - - if (trimmed.startsWith("- github:")) { - if (current?.github && current?.discord) { - contributors.push(current as Contributor); - } - current = { github: trimmed.replace("- github:", "").trim() }; - } else if (trimmed.startsWith("discord:") && current) { - current.discord = trimmed.replace("discord:", "").trim().replace(/^["']|["']$/g, ""); - } else if (trimmed.startsWith("name:") && current) { - current.name = trimmed.replace("name:", "").trim(); - } - } - - // Don't forget the last entry - if (current?.github && current?.discord) { - contributors.push(current as Contributor); - } - - return contributors; -} - -function loadContributors(): Map { +async function loadContributors(): Promise> { const map = new Map(); - try { - // Resolve path relative to the script location (scripts/ dir → repo root) - const scriptDir = new URL(".", import.meta.url).pathname; - const raw = readFileSync( - join(scriptDir, "..", "contributors.yml"), - "utf-8" - ); - const entries = parseContributorsYaml(raw); + const uri = process.env.MONGODB_URI; + if (!uri) { + console.warn("Warning: MONGODB_URI not set, contributor lookups disabled"); + return map; + } - for (const c of entries) { - map.set(c.github.toLowerCase(), c); + const client = new MongoClient(uri); + try { + await client.connect(); + const db = client.db("hive"); + const docs = await db.collection("contributors").find().toArray(); + + for (const doc of docs) { + map.set((doc.github as string).toLowerCase(), { + github: (doc.githubDisplay as string) ?? (doc.github as string), + discord: doc.discord as string, + name: doc.name as string | undefined, + }); } - } catch { - console.warn("Warning: could not load contributors.yml"); + + console.log(`Loaded ${map.size} contributors from MongoDB`); + } catch (err) { + console.warn(`Warning: could not load contributors from MongoDB: ${err}`); + } finally { + await client.close(); } return map; @@ -295,7 +273,7 @@ function formatBountyNotification(bounty: BountyResult): string { msg += `PR: ${bounty.pr.html_url}\n`; if (!bounty.discordId) { - msg += `\n_\u{1F517} @${bounty.contributor}: link your Discord in \`contributors.yml\` to get pinged!_`; + msg += `\n_\u{1F517} @${bounty.contributor}: use \`/link-github\` in Discord to get pinged!_`; } return msg; @@ -464,7 +442,7 @@ async function main() { process.exit(1); } - const contributors = loadContributors(); + const contributors = await loadContributors(); if (mode === "notify") { // Single bounty notification