Skip to content

[REGISTER] GIT Going with GitHub - March 2026 #2

[REGISTER] GIT Going with GitHub - March 2026

[REGISTER] GIT Going with GitHub - March 2026 #2

name: Learning Room PR Bot
# Automated validation and feedback for student pull requests
on:
pull_request:
types: [opened, edited, synchronize, reopened]
paths:
- 'learning-room/**'
pull_request_review:
types: [submitted]
issue_comment:
types: [created]
jobs:
welcome-first-timer:
name: Welcome First-Time Contributors
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.event.action == 'opened'
steps:
- name: Check if first-time contributor
uses: actions/github-script@v7
with:
script: |
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all',
creator: context.payload.pull_request.user.login
});
const isFirstPR = prs.length === 1;
if (isFirstPR) {
const welcomeBody = [
'## Welcome to Your First Pull Request!',
'',
'Hi @' + context.payload.pull_request.user.login + '! This is your first PR in this repository. Congratulations on taking this important step in your open source journey!',
'',
'### What Happens Next',
'',
'1. **Automated Checks** \u2014 This bot will validate your PR (see checks below)',
'2. **Peer Review** \u2014 A facilitator or peer will review your changes',
'3. **Feedback** \u2014 You may receive suggestions for improvements',
'4. **Merge** \u2014 Once approved, your changes become part of the project',
'',
'### While You Wait',
'',
'- Check the automated validation report below',
'- Review the [PR guidelines](../../docs/05-working-with-pull-requests.md)',
'- Look at other open PRs to learn from examples',
'',
'**Remember:** Every experienced contributor started exactly where you are now. Questions are welcome!',
'',
'---',
'*This is an automated message from the Learning Room Bot. Need help? Mention @facilitator in a comment.*'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: welcomeBody
});
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: ['first-time-contributor', 'needs-review']
});
}
validate-pr:
name: Validate PR Requirements
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run PR validation
id: validate
run: node .github/scripts/validate-pr.js
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
- name: Post validation results
if: always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const results = JSON.parse(fs.readFileSync('validation-results.json', 'utf8'));
let status = results.passed ? '**Validation Passed**' : '**Validation Needs Attention**';
let emoji = results.passed ? '' : '';
let body = `## ${emoji} PR Validation Report\n\n${status}\n\n`;
// Required checks
body += `### Required Checks\n\n`;
results.required.forEach(check => {
const icon = check.passed ? '' : '';
body += `${icon} **${check.name}**\n`;
if (!check.passed) {
body += ` ${check.message}\n`;
if (check.help) {
body += ` *${check.help}*\n`;
}
}
body += `\n`;
});
// Optional improvements
if (results.suggestions.length > 0) {
body += `### Suggestions for Improvement\n\n`;
body += `*These are optional but will make your contribution even better:*\n\n`;
results.suggestions.forEach(suggestion => {
body += `- ${suggestion.message}\n`;
if (suggestion.help) {
body += ` *${suggestion.help}*\n`;
}
});
body += `\n`;
}
// Accessibility checks
if (results.accessibility.length > 0) {
body += `### Accessibility Analysis\n\n`;
results.accessibility.forEach(item => {
const icon = item.type === 'error' ? '' : item.type === 'warning' ? '' : '';
body += `${icon} **${item.title}**\n`;
body += ` ${item.message}\n`;
if (item.file) {
body += ` \`${item.file}\`${item.line ? ` (line ${item.line})` : ''}\n`;
}
if (item.fix) {
body += ` **Fix:** ${item.fix}\n`;
}
body += `\n`;
});
}
// Learning resources
body += `### Learning Resources\n\n`;
body += `Based on your changes, these guides might help:\n\n`;
results.resources.forEach(resource => {
body += `- [${resource.title}](${resource.url})\n`;
});
body += `\n---\n`;
body += `*Automated validation by Learning Room Bot. Last updated: ${new Date().toISOString()}*\n`;
body += `*Questions? Check [PR Guidelines](../../docs/05-working-with-pull-requests.md) or mention @facilitator*`;
// Find and update existing bot comment, or create new one
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('PR Validation Report')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: body
});
}
// Apply labels
const labels = ['documentation'];
if (!results.passed) {
labels.push('needs-work');
}
if (results.accessibility.some(a => a.type === 'error')) {
labels.push('accessibility');
}
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: labels
});
- name: Set status check
if: always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const results = JSON.parse(fs.readFileSync('validation-results.json', 'utf8'));
const state = results.passed ? 'success' : 'failure';
const description = results.passed
? 'All validation checks passed!'
: 'Some validation checks need attention';
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.payload.pull_request.head.sha,
state: state,
target_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/pull/${context.payload.pull_request.number}#issuecomment`,
description: description,
context: 'Learning Room Bot / Validation'
});
celebrate-review:
name: Celebrate Peer Review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request_review' && github.event.review.state == 'approved'
steps:
- name: Congratulate reviewer
uses: actions/github-script@v7
with:
script: |
const reviewBody = [
'## Peer Review Complete!',
'',
'Great work, @' + context.payload.review.user.login + '! You\'ve completed a peer review.',
'',
'**Why this matters:** Code review is one of the most valuable skills in open source. You\'re helping ensure quality, sharing knowledge, and building community trust.',
'',
'@' + context.payload.pull_request.user.login + ' \u2014 your PR has been approved! A facilitator will merge it soon.',
'',
'---',
'*Learning Room Bot celebrates your collaboration*'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: reviewBody
});
respond-to-questions:
name: Respond to Common Questions
runs-on: ubuntu-latest
if: github.event_name == 'issue_comment' && github.event.issue.pull_request
steps:
- name: Auto-respond to keywords
uses: actions/github-script@v7
with:
script: |
const comment = context.payload.comment.body.toLowerCase();
const author = context.payload.comment.user.login;
// Don't respond to bots
if (context.payload.comment.user.type === 'Bot') return;
let response = null;
if (comment.includes('@bot help') || comment.includes('need help')) {
response = [
'Hi @' + author + '! Here are some helpful resources:',
'',
'**Guides:**',
'- [Working with Pull Requests](../../docs/05-working-with-pull-requests.md)',
'- [Merge Conflicts](../../docs/06-merge-conflicts.md)',
'- [Culture & Etiquette](../../docs/07-culture-etiquette.md)',
'',
'**Common Issues:**',
'- **Merge conflicts?** Check the [Merge Conflicts guide](../../docs/06-merge-conflicts.md)',
'- **Need to update your PR?** Make changes on your branch and push again',
'- **Validation failing?** Read the validation report above for specific fixes',
'',
'**Still stuck?** Mention `@facilitator` in a comment for human help!'
].join('\n');
}
if (comment.includes('merge conflict')) {
response = [
'Hi @' + author + '! I see you\'re dealing with a merge conflict.',
'',
'**Quick steps to resolve:**',
'',
'1. Go to the "Files changed" tab',
'2. Click "Resolve conflicts" button',
'3. GitHub\'s conflict editor will show you both versions',
'4. Choose which lines to keep (remove the `<<<<<<<`, `=======`, `>>>>>>>` markers)',
'5. Click "Mark as resolved"',
'6. Commit the merge',
'',
'**Need detailed guidance?** See [Merge Conflicts Guide](../../docs/06-merge-conflicts.md)',
'',
'**For screen readers:** The conflict editor works best in Firefox or Edge with NVDA/JAWS. VoiceOver users: consider using github.dev (press the "." key) for better conflict resolution.'
].join('\n');
}
if (comment.includes('how do i') && comment.includes('request review')) {
response = [
'Hi @' + author + '! To request a review:',
'',
'1. On your PR page, find the "Reviewers" section in the right sidebar',
'2. Click the gear icon next to "Reviewers"',
'3. Start typing a facilitator or peer\'s username',
'4. Select them from the dropdown',
'5. They\'ll be notified automatically',
'',
'**Screen reader users:** The reviewers section is after the main PR description. Navigate to the complementary landmark "Request reviewers".'
].join('\n');
}
if (response) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: response + '\n\n---\n*Auto-response from Learning Room Bot*'
});
}