Skip to content
Closed
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
87 changes: 87 additions & 0 deletions .github/workflows/auto-assign.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Auto Assign Issue

on:
issue_comment:
types: [created]

permissions:
issues: write
pull-requests: write

jobs:
auto-assign:
runs-on: ubuntu-latest
steps:
- name: Auto-assign issue to commenter
uses: actions/github-script@v7
with:
script: |
const commentBody = context.payload.comment.body.trim().toLowerCase();

// List of trigger phrases/keywords
const triggerKeywords = [
'take',
'assign',
'.take',
'/take',
'/assign',
'i want to work on this',
'i would like to work on this'
];

// Check if comment matches any trigger keyword or starts with one
const isTrigger = triggerKeywords.some(keyword => {
return commentBody === keyword || commentBody.startsWith(keyword + ' ') || commentBody.startsWith(keyword + '\n');
});

if (!isTrigger) {
console.log("Comment does not trigger auto-assignment.");
return;
}

const issue = context.payload.issue;

// Skip pull requests
if (issue.pull_request) {
console.log("This comment is on a pull request, skipping auto-assign.");
return;
}

const commenter = context.payload.comment.user.login;

// Check if the issue is already assigned
if (issue.assignees && issue.assignees.length > 0) {
const currentAssignees = issue.assignees.map(a => a.login);
if (currentAssignees.includes(commenter)) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `Hi @${commenter}, you are already assigned to this issue! Please proceed with your contribution. 😊`
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `Hi @${commenter}, this issue is already assigned to @${currentAssignees.join(', @')}. Please look for other unassigned issues to contribute to! Thank you.`
});
}
return;
}

// Assign the issue to the commenter
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
assignees: [commenter]
});

// Post confirmation comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `Hi @${commenter}, this issue has been successfully assigned to you! 🎉\n\nMake sure to read our [Contributing Guidelines](CONTRIBUTING.md) and submit your PR within the GSSoC timeframe. Happy coding! 🚀`
});
24 changes: 0 additions & 24 deletions .github/workflows/auto-file-labels.yml

This file was deleted.

26 changes: 0 additions & 26 deletions .github/workflows/auto-label-issues.yml

This file was deleted.

76 changes: 0 additions & 76 deletions .github/workflows/auto-label-prs.yml

This file was deleted.

131 changes: 131 additions & 0 deletions .github/workflows/difficulty.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
name: PR Difficulty Labeler

on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review, edited]

permissions:
issues: write
pull-requests: write

jobs:
detect-difficulty:
name: Assign difficulty label
runs-on: ubuntu-latest

steps:
- name: Analyze PR and assign difficulty label
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const prNum = pr.number;
const repo = { owner: context.repo.owner, repo: context.repo.repo };

// ── Label catalogue ────────────────────────────────────────────
const DIFFICULTY_LABELS = ['level:beginner', 'level:intermediate', 'level:advanced', 'level:critical'];

const LABEL_META = {
'level:beginner': { color: '0E8A16', description: 'Small, low-risk change (≤3 files, ≤50 lines)' },
'level:intermediate': { color: 'FBCA04', description: 'Medium-sized feature or fix (≤10 files, ≤250 lines)' },
'level:advanced': { color: 'D93F0B', description: 'Large feature or broad change (≤25 files, ≤800 lines)' },
'level:critical': { color: 'B60205', description: 'Major / core-repository change' },
};

const CORE_PATTERNS = [
/^\.github\/workflows\//,
/package(-lock)?\.json$/,
/^src\/App\.(js|jsx|ts|tsx)$/,
/^src\/main\.(js|jsx|ts|tsx)$/,
/^src\/config\//,
/^server\/server\.js$/,
/^server\/routes\//,
/^server\/middleware\//,
];

const ALWAYS_CRITICAL_PATTERNS = [
/^src\/main\.(js|jsx|ts|tsx)$/,
/^server\/server\.js$/,
/^server\/routes\//,
];

// ── Helpers ────────────────────────────────────────────────────
async function ensureLabels() {
await Promise.all(
Object.entries(LABEL_META).map(async ([name, { color, description }]) => {
try {
await github.rest.issues.getLabel({ ...repo, name });
} catch (err) {
if (err.status !== 404) throw err;
await github.rest.issues.createLabel({ ...repo, name, color, description });
console.log(`✅ Created label: ${name}`);
}
})
);
}

async function getFiles() {
return github.paginate(github.rest.pulls.listFiles, {
...repo,
pull_number: prNum,
per_page: 100,
});
}

async function getCurrentLabels() {
const { data } = await github.rest.issues.get({ ...repo, issue_number: prNum });
return data.labels.map(l => (typeof l === 'string' ? l : l.name));
}

function classify({ filesChanged, linesChanged, hasCoreChanges, hasAlwaysCritical }) {
if (hasAlwaysCritical) return 'level:critical';
if (filesChanged > 25 || linesChanged > 800) return 'level:critical';
if (hasCoreChanges && (filesChanged > 10 || linesChanged > 300))
return 'level:critical';
if (filesChanged <= 3 && linesChanged <= 50) return 'level:beginner';
if (filesChanged <= 10 && linesChanged <= 250) return 'level:intermediate';
return 'level:advanced';
}

async function applyLabel(targetLabel) {
const current = await getCurrentLabels();

const staleLabels = current.filter(l => DIFFICULTY_LABELS.includes(l) && l !== targetLabel);

await Promise.all(
staleLabels.map(name =>
github.rest.issues.removeLabel({ ...repo, issue_number: prNum, name })
.then(() => console.log(`🗑 Removed stale difficulty label: ${name}`))
)
);

if (current.includes(targetLabel) && staleLabels.length === 0) {
console.log(`✔ Difficulty label already correct: ${targetLabel} — skipping.`);
return;
}

if (!current.includes(targetLabel)) {
await github.rest.issues.addLabels({ ...repo, issue_number: prNum, labels: [targetLabel] });
console.log(`🏷 Applied difficulty label: ${targetLabel}`);
}
}

// ── Main ───────────────────────────────────────────────────────
const files = await getFiles();

const additions = files.reduce((s, f) => s + f.additions, 0);
const deletions = files.reduce((s, f) => s + f.deletions, 0);
const linesChanged = additions + deletions;
const filesChanged = files.length;
const hasCoreChanges = files.some(f => CORE_PATTERNS.some(p => p.test(f.filename)));
const hasAlwaysCritical = files.some(f => ALWAYS_CRITICAL_PATTERNS.some(p => p.test(f.filename)));

const targetLabel = classify({ filesChanged, linesChanged, hasCoreChanges, hasAlwaysCritical });

console.log(`PR #${prNum} — "${pr.title}"`);
console.log(` Files : ${filesChanged} | Lines: +${additions} -${deletions} = ${linesChanged}`);
console.log(` Core changes: ${hasCoreChanges} | Always-critical paths: ${hasAlwaysCritical}`);
console.log(` → Difficulty: ${targetLabel}`);

await ensureLabels();
await applyLabel(targetLabel);
Loading
Loading