Files
nuclei-templates/.github/workflows/autoassign.yml
2025-10-23 20:25:43 +05:30

165 lines
6.7 KiB
YAML

name: Auto-assign on open
on:
issues:
types: [opened, reopened]
# Use pull_request_target so the job has permissions on PRs from forks.
pull_request_target:
types: [opened, ready_for_review, reopened]
permissions:
contents: read
issues: write
pull-requests: write
concurrency:
group: auto-assign-on-open
cancel-in-progress: false
jobs:
assign:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
with:
script: |
const ORG = context.repo.owner;
const REPO = context.repo.repo;
// ======= CONFIG: put your user pools here =======
// Usernames must be GitHub logins and collaborators on this repo.
const ISSUE_ASSIGNEES = [
"princechaddha","pussycat0x","ritikchaddha","DhiyaneshGeek","akokonunes","theamanrawat"
];
const REVIEW_POOL = [
"pussycat0x","ritikchaddha","DhiyaneshGeek","akokonunes","theamanrawat"
];
const LOOKBACK_DAYS = 7; // only used as a tie-break fairness metric
// ================================================
function toLowerAll(xs){ return xs.map(x => x.toLowerCase()); }
const issuePool = new Set(toLowerAll(ISSUE_ASSIGNEES));
const reviewPool = new Set(toLowerAll(REVIEW_POOL));
const sinceISO = new Date(Date.now() - LOOKBACK_DAYS*24*60*60*1000).toISOString();
// Simple "least recent then fewest in lookback" picker
function initMap(arr){ const m=new Map(); for(const a of arr) m.set(a,0); return m; }
const issueCounts = initMap(issuePool);
const prAssigneeCounts = initMap(reviewPool);
const prReviewerCounts = initMap(reviewPool);
const lastIssueAssign = new Map();
const lastPrAssignee = new Map();
const lastPrReviewer = new Map();
function pick(countsMap, lastMap, exclude=new Set()){
const cands = [...countsMap.keys()].filter(x => !exclude.has(x));
if (!cands.length) return null;
cands.sort((a,b)=>{
const ca=countsMap.get(a)||0, cb=countsMap.get(b)||0;
if (ca!==cb) return ca-cb;
const ta=lastMap.get(a)?.getTime()||0, tb=lastMap.get(b)?.getTime()||0;
if (ta!==tb) return ta-tb;
return a.localeCompare(b);
});
return cands[0];
}
async function buildIssueStats(){
for await (const page of github.paginate.iterator(
github.rest.issues.listForRepo,
{ owner: ORG, repo: REPO, state: 'all', since: sinceISO, per_page: 100 }
)){
for (const it of page.data){
if (it.pull_request) continue;
const ts = new Date(it.created_at);
if (ts < new Date(sinceISO)) continue;
for (const a of (it.assignees||[])){
const l=a.login.toLowerCase();
if (issuePool.has(l)){
issueCounts.set(l,(issueCounts.get(l)||0)+1);
const lastTs = lastIssueAssign.get(l);
if (!lastTs || ts > lastTs) lastIssueAssign.set(l, ts);
}
}
}
}
}
async function buildPrStats(){
for await (const page of github.paginate.iterator(
github.rest.pulls.list,
{ owner: ORG, repo: REPO, state: 'all', per_page: 100 }
)){
for (const pr of page.data){
const ts = new Date(pr.created_at);
if (ts < new Date(sinceISO)) continue;
// Count all assignees (plural)
for (const a of (pr.assignees||[])){
const l = a.login.toLowerCase();
if (reviewPool.has(l)){
prAssigneeCounts.set(l,(prAssigneeCounts.get(l)||0)+1);
const lastTs = lastPrAssignee.get(l);
if (!lastTs || ts > lastTs) lastPrAssignee.set(l, ts);
}
}
// Count all reviewers separately
for (const r of (pr.requested_reviewers||[])){
const l=r.login.toLowerCase();
if (reviewPool.has(l)){
prReviewerCounts.set(l,(prReviewerCounts.get(l)||0)+1);
const lastTs = lastPrReviewer.get(l);
if (!lastTs || ts > lastTs) lastPrReviewer.set(l, ts);
}
}
}
}
}
// Build stats once per run (cheap enough for small repos)
await Promise.all([buildIssueStats(), buildPrStats()]);
// Determine context
if (context.eventName === 'issues') {
const issue = context.payload.issue;
if (issue.pull_request) return; // guard
if ((issue.assignees||[]).length>0) return;
const pickee = pick(issueCounts, lastIssueAssign);
if (!pickee) return;
await github.rest.issues.addAssignees({
owner: ORG, repo: REPO, issue_number: issue.number, assignees: [pickee]
});
} else if (context.eventName === 'pull_request_target') {
const pr = context.payload.pull_request;
const prNum = pr.number;
const author = (pr.user?.login||"").toLowerCase();
const assignee = pr.assignee?.login?.toLowerCase();
// Ensure one assignee (use assignee counts only)
let finalAssignee = assignee;
if (!finalAssignee) {
const a = pick(prAssigneeCounts, lastPrAssignee, new Set([author]));
if (a) {
await github.rest.issues.addAssignees({
owner: ORG, repo: REPO, issue_number: prNum, assignees: [a]
});
finalAssignee = a;
}
}
// One reviewer, not the author, not the assignee (use reviewer counts only)
const already = new Set((pr.requested_reviewers||[]).map(x=>x.login.toLowerCase()));
if (already.size === 0 && !pr.draft) {
const exclude = new Set([author, finalAssignee].filter(Boolean));
const reviewer = pick(prReviewerCounts, lastPrReviewer, exclude);
if (reviewer) {
await github.rest.pulls.requestReviewers({
owner: ORG, repo: REPO, pull_number: prNum, reviewers: [reviewer]
});
}
}
}