Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
es6: true,
browser: true,
node: true,
jest: true,
},
parserOptions: {
sourceType: 'module',
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/manage-issue-header.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,34 @@ jobs:
script: |
const script = require('./scripts/manage-issue-header.js');
await script({github, context, core});
good-first-issue-comment:
runs-on: ubuntu-latest
if: >-
github.event.action == 'labeled' && github.event.label.name == 'good first issue'
steps:
- name: Generate GitHub token
id: generate-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.LE_BOT_APP_ID }}
private-key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
- name: Checkout .github repository
uses: actions/checkout@v6
with:
repository: learningequality/.github
ref: main
token: ${{ steps.generate-token.outputs.token }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run script
uses: actions/github-script@v8
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |
const script = require('./scripts/good-first-issue-comment.js');
await script({github, context, core});
39 changes: 39 additions & 0 deletions docs/community-automations.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,45 @@ In `scripts/contants.js` set:
- `BOT_MESSAGE_ISSUE_NOT_OPEN`: _Issue not open for contribution_ message text
- `BOT_MESSAGE_ALREADY_ASSIGNED`: _Issue already assigned_ message text

## `/assign` self-assignment

When a contributor comments `/assign` (exact match, trimmed) on an issue:

| Issue state | Action | Bot message | Slack |
|-------------|--------|-------------|-------|
| Not `help wanted` | Decline | `BOT_MESSAGE_ISSUE_NOT_OPEN` | `#support-dev-notifications` |
| `help wanted` + assigned to commenter | No-op | - | - |
| `help wanted` + assigned to someone else | Decline | `BOT_MESSAGE_ALREADY_ASSIGNED` | `#support-dev-notifications` |
| `help wanted` + NOT `good first issue` | Decline | `BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE` | `#support-dev-notifications` |
| `help wanted` + `good first issue` + at limit | Decline | Dynamic message with assignments and cooldowns | `#support-dev-notifications` |
| `help wanted` + `good first issue` + under limit | Assign | `BOT_MESSAGE_ASSIGN_SUCCESS` | `#support-dev-notifications` |

**Cross-repo limit:** Contributors can have up to 2 assigned issues across all community repos (`COMMUNITY_REPOS`).

**7-day cooldown:** Issues unassigned within the last 7 days count toward the limit. The limit check is: `currentAssignments + recentUnassignments >= MAX_ASSIGNED_ISSUES`.

**Slack notifications:** All `/assign` activity goes to `#support-dev-notifications` only. It does not trigger `#support-dev` notifications.

## Keyword detection on `good first issue` issues

When existing keyword detection triggers on an unassigned `good first issue` issue, the bot reply includes guidance about the `/assign` command (`BOT_MESSAGE_KEYWORD_GOOD_FIRST_ISSUE`) instead of the normal no-reply behavior.

In `scripts/constants.js` set:
- `BOT_MESSAGE_ASSIGN_SUCCESS`: Assignment confirmation message
- `BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE`: Decline message for non-GFI issues
- `BOT_MESSAGE_KEYWORD_GOOD_FIRST_ISSUE`: Keyword reply with `/assign` guidance

# `good-first-issue-comment`

Posts a guidance comment when the `good first issue` label is applied to an issue (triggered via the `manage-issue-header` workflow). Explains the `/assign` command, issue limits, cooldown, and links to contributing guidelines.

- Only posts if the issue also has `help wanted` label
- Deletes any previous guidance comment from the bot before posting a new one
- Identified by the `<!-- ASSIGN_GUIDANCE -->` HTML comment marker

In `scripts/constants.js` set:
- `BOT_MESSAGE_GOOD_FIRST_ISSUE_GUIDANCE`: Guidance message text

# `contributor-pr-reply`

Sends reply to a community pull requests.
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"scripts": {
"lint": "eslint .",
"format": "prettier --write ."
"format": "prettier --write .",
"test": "jest"
},
"dependencies": {
"axios": "^1.13.5",
Expand All @@ -13,6 +14,7 @@
"devDependencies": {
"eslint": "^8.57.0",
"eslint-config-prettier": "^10.1.8",
"jest": "^30.3.0",
"prettier": "^3.8.1"
}
}
60 changes: 59 additions & 1 deletion scripts/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ const KEYWORDS_DETECT_ASSIGNMENT_REQUEST = [
];

const ISSUE_LABEL_HELP_WANTED = 'help wanted';
const ISSUE_LABEL_GOOD_FIRST_ISSUE = 'good first issue';
const MAX_ASSIGNED_ISSUES = 2;
const COOLDOWN_DAYS = 7;
const ASSIGN_GUIDANCE_MARKER = '<!-- ASSIGN_GUIDANCE -->';
const LABEL_COMMUNITY_REVIEW = 'community-review';

// Will be attached to bot messages when not empty
Expand All @@ -92,7 +96,52 @@ const BOT_MESSAGE_ISSUE_NOT_OPEN = `Hi! 👋 \n\n Thanks so much for your intere

const BOT_MESSAGE_ALREADY_ASSIGNED = `Hi! 👋 \n\n Thanks so much for your interest! **This issue is already assigned. Visit [Contributing guidelines](https://learningequality.org/contributing-to-our-open-code-base) to learn about the contributing process and how to find suitable issues. If there are no unassigned 'help wanted' issues available, please wait until new ones are added.** \n\n We really appreciate your willingness to help. 😊${GSOC_NOTE}`;

const BOT_MESSAGE_PULL_REQUEST = (author) => `👋 Hi @${author}, thanks for contributing! \n\n **For the review process to begin, please verify that the following is satisfied:**\n\n- [ ] **Contribution is aligned with our [contributing guidelines](https://learningequality.org/contributing-to-our-open-code-base)**\n- [ ] **Pull request description has correctly filled _AI usage_ section & follows our AI guidance:**\n\n <details>\n <summary><b><i>AI guidance</i></b></summary>\n\n <br>\n\n **State explicitly whether you didn't use or used AI & how.**\n\n If you used it, ensure that the PR is aligned with [Using AI](https://learningequality.org/contributing-to-our-open-code-base/#using-generative-ai) as well as our DEEP framework. DEEP asks you:\n\n - **Disclose** — Be open about when you've used AI for support.\n - **Engage critically** — Question what is generated. Review code for correctness and unnecessary complexity.\n - **Edit** — Review and refine AI output. Remove unnecessary code and verify it still works after your edits.\n - **Process sharing** — Explain how you used the AI so others can learn.\n\n <br>\n\n Examples of good disclosures:\n\n > "I used Claude Code to implement the component, prompting it to follow the pattern in ComponentX. I reviewed the generated code, removed unnecessary error handling, and verified the tests pass."\n\n > "I brainstormed the approach with Gemini, then had it write failing tests for the feature. After reviewing the tests, I used Claude Code to generate the implementation. I refactored the output to reduce verbosity and ran the full test suite."\n\n </details>\n\nAlso check that issue requirements are satisfied & you ran \`pre-commit\` locally. \n\n**Pull requests that don't follow the guidelines will be closed.**\n\n**Reviewer assignment can take up to 2 weeks.**`;
const BOT_MESSAGE_GOOD_FIRST_ISSUE_GUIDANCE =
`${ASSIGN_GUIDANCE_MARKER}\n\n` +
`Hi! 👋\n\n` +
`This issue is available for contribution and supports ` +
`**self-assignment**. Here's how to get started:\n\n` +
`- **Comment \`/assign\` to assign yourself** to this issue\n` +
`- You can have up to **${MAX_ASSIGNED_ISSUES} issues** assigned ` +
`at a time across all community repos\n` +
`- Dropping an issue has a **${COOLDOWN_DAYS}-day cooldown** ` +
`before the slot opens up\n` +
`- **Link your pull request** to this issue when you submit it` +
`\n\n📖 **Read the [Contributing guidelines]` +
`(https://learningequality.org/contributing-to-our-open-code-base/)` +
` before starting.**${GSOC_NOTE}`;

const BOT_MESSAGE_ASSIGN_SUCCESS =
`Hi! 👋\n\n` +
`You've been assigned to this issue. Here's what to do next:\n\n` +
`- **Read the issue description** carefully and make sure ` +
`you understand the requirements\n` +
`- **Link your pull request** to this issue when you submit it\n` +
`- If you can no longer work on this, **unassign yourself** ` +
`so others can pick it up\n\n` +
`Good luck! 😊${GSOC_NOTE}`;

const BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE =
`Hi! 👋\n\n` +
`Self-assignment via \`/assign\` is only available for issues ` +
`labeled **\`good first issue\`**. This issue does not have ` +
`that label.\n\n` +
`Visit [Contributing guidelines]` +
`(https://learningequality.org/contributing-to-our-open-code-base/)` +
` to learn about the contributing process and how to find ` +
`suitable issues. 😊${GSOC_NOTE}`;

const BOT_MESSAGE_KEYWORD_GOOD_FIRST_ISSUE =
`Hi! 👋\n\n` +
`Thanks for your interest! This issue supports ` +
`**self-assignment**. **Comment \`/assign\` to assign ` +
`yourself.**\n\n` +
`Visit [Contributing guidelines]` +
`(https://learningequality.org/contributing-to-our-open-code-base/)` +
` to learn about the contributing process. 😊${GSOC_NOTE}`;

const BOT_MESSAGE_PULL_REQUEST = author =>
`👋 Hi @${author}, thanks for contributing! \n\n **For the review process to begin, please verify that the following is satisfied:**\n\n- [ ] **Contribution is aligned with our [contributing guidelines](https://learningequality.org/contributing-to-our-open-code-base)**\n- [ ] **Pull request description has correctly filled _AI usage_ section & follows our AI guidance:**\n\n <details>\n <summary><b><i>AI guidance</i></b></summary>\n\n <br>\n\n **State explicitly whether you didn't use or used AI & how.**\n\n If you used it, ensure that the PR is aligned with [Using AI](https://learningequality.org/contributing-to-our-open-code-base/#using-generative-ai) as well as our DEEP framework. DEEP asks you:\n\n - **Disclose** — Be open about when you've used AI for support.\n - **Engage critically** — Question what is generated. Review code for correctness and unnecessary complexity.\n - **Edit** — Review and refine AI output. Remove unnecessary code and verify it still works after your edits.\n - **Process sharing** — Explain how you used the AI so others can learn.\n\n <br>\n\n Examples of good disclosures:\n\n > "I used Claude Code to implement the component, prompting it to follow the pattern in ComponentX. I reviewed the generated code, removed unnecessary error handling, and verified the tests pass."\n\n > "I brainstormed the approach with Gemini, then had it write failing tests for the feature. After reviewing the tests, I used Claude Code to generate the implementation. I refactored the output to reduce verbosity and ran the full test suite."\n\n </details>\n\nAlso check that issue requirements are satisfied & you ran \`pre-commit\` locally. \n\n**Pull requests that don't follow the guidelines will be closed.**\n\n**Reviewer assignment can take up to 2 weeks.**`;

const HOLIDAY_MESSAGE = `Season's greetings! 👋 \n\n We'd like to thank everyone for another year of fruitful collaborations, engaging discussions, and for the continued support of our work. **Learning Equality will be on holidays from December 22 to January 5.** We look forward to much more in the new year and wish you a very happy holiday season!${GSOC_NOTE}`;

Expand All @@ -119,8 +168,17 @@ module.exports = {
CLOSE_CONTRIBUTORS,
KEYWORDS_DETECT_ASSIGNMENT_REQUEST,
ISSUE_LABEL_HELP_WANTED,
GSOC_NOTE,
ISSUE_LABEL_GOOD_FIRST_ISSUE,
MAX_ASSIGNED_ISSUES,
COOLDOWN_DAYS,
ASSIGN_GUIDANCE_MARKER,
BOT_MESSAGE_ISSUE_NOT_OPEN,
BOT_MESSAGE_ALREADY_ASSIGNED,
BOT_MESSAGE_GOOD_FIRST_ISSUE_GUIDANCE,
BOT_MESSAGE_ASSIGN_SUCCESS,
BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE,
BOT_MESSAGE_KEYWORD_GOOD_FIRST_ISSUE,
BOT_MESSAGE_PULL_REQUEST,
BOT_MESSAGE_RTIBBLESBOT_REVIEW,
RTIBBLESBOT_USERNAME,
Expand Down
66 changes: 66 additions & 0 deletions scripts/constants.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const constants = require('./constants');

describe('assign-related constants', () => {
test('ISSUE_LABEL_GOOD_FIRST_ISSUE is defined', () => {
expect(constants.ISSUE_LABEL_GOOD_FIRST_ISSUE).toBe('good first issue');
});

test('MAX_ASSIGNED_ISSUES is 2', () => {
expect(constants.MAX_ASSIGNED_ISSUES).toBe(2);
});

test('COOLDOWN_DAYS is 7', () => {
expect(constants.COOLDOWN_DAYS).toBe(7);
});

test('ASSIGN_GUIDANCE_MARKER is an HTML comment marker', () => {
expect(constants.ASSIGN_GUIDANCE_MARKER).toBe('<!-- ASSIGN_GUIDANCE -->');
});

test('GSOC_NOTE is exported', () => {
expect(constants.GSOC_NOTE).toBeDefined();
expect(typeof constants.GSOC_NOTE).toBe('string');
});
});

describe('bot messages for /assign', () => {
test('BOT_MESSAGE_GOOD_FIRST_ISSUE_GUIDANCE contains marker and key info', () => {
const msg = constants.BOT_MESSAGE_GOOD_FIRST_ISSUE_GUIDANCE;
expect(msg).toBeDefined();
expect(msg).toContain(constants.ASSIGN_GUIDANCE_MARKER);
expect(msg).toContain('/assign');
expect(msg).toContain('self-assignment');
expect(msg).toContain(String(constants.MAX_ASSIGNED_ISSUES));
expect(msg).toContain(String(constants.COOLDOWN_DAYS));
expect(msg).toContain('Contributing guidelines');
});

test('BOT_MESSAGE_ASSIGN_SUCCESS contains assignment confirmation', () => {
const msg = constants.BOT_MESSAGE_ASSIGN_SUCCESS;
expect(msg).toBeDefined();
expect(msg).toContain('assigned');
expect(msg).toContain('Link your pull request');
});

test('BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE explains GFI requirement', () => {
const msg = constants.BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE;
expect(msg).toBeDefined();
expect(msg).toContain('good first issue');
expect(msg).toContain('/assign');
});

test('BOT_MESSAGE_KEYWORD_GOOD_FIRST_ISSUE guides to /assign', () => {
const msg = constants.BOT_MESSAGE_KEYWORD_GOOD_FIRST_ISSUE;
expect(msg).toBeDefined();
expect(msg).toContain('/assign');
expect(msg).toContain('self-assignment');
});

test('all new bot messages include GSOC_NOTE', () => {
const gsoc = constants.GSOC_NOTE;
expect(constants.BOT_MESSAGE_GOOD_FIRST_ISSUE_GUIDANCE).toContain(gsoc);
expect(constants.BOT_MESSAGE_ASSIGN_SUCCESS).toContain(gsoc);
expect(constants.BOT_MESSAGE_ASSIGN_NOT_GOOD_FIRST_ISSUE).toContain(gsoc);
expect(constants.BOT_MESSAGE_KEYWORD_GOOD_FIRST_ISSUE).toContain(gsoc);
});
});
Loading