From ed4df9f8c26fe35412e816aa11d17bf0253a1e91 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Date: Wed, 5 Aug 2026 22:48:30 +0500 Subject: [PATCH] Reconcile the ai-assisted label for exempt authors too The exempt early-return skipped disclosure reconciliation, so a stale ai-assisted label on a maintainer or member issue could never clear. Disclosure labeling now mirrors the body for every author; the exemption covers only the template and length gates. Per Greptile's follow-up finding on #518. AI-assisted: written with an AI agent under maintainer direction. Co-authored-by: Cursor --- scripts/github/issue-gate.mjs | 13 ++++++++----- tests/github-issue-gate.test.mjs | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/scripts/github/issue-gate.mjs b/scripts/github/issue-gate.mjs index b3b5a430f..2c47112f1 100644 --- a/scripts/github/issue-gate.mjs +++ b/scripts/github/issue-gate.mjs @@ -139,11 +139,17 @@ export function evaluateIssue(issue, options = {}) { shouldReopen: false, }; + // Disclosure labeling mirrors the body for every author, exempt or not; the + // exemption below only covers the template and length gates. + const body = String(issue.body || ''); + base.aiAssisted = AI_DISCLOSURE.test(body); + if (base.aiAssisted && !labels.has(AI_LABEL)) base.labelsToAdd.push(AI_LABEL); + if (!base.aiAssisted && labels.has(AI_LABEL)) base.labelsToRemove.push(AI_LABEL); + if (maintainers.has(author) || EXEMPT_ASSOCIATIONS.has(issue.authorAssociation)) { return { ...base, exempt: true }; } - const body = String(issue.body || ''); const bodyHeadings = new Set(extractHeadings(body)); const anyTemplateHeading = templates.some( (template) => template.headings.some((heading) => bodyHeadings.has(heading)), @@ -179,9 +185,6 @@ export function evaluateIssue(issue, options = {}) { if (verdict !== 'reject' && reasons.length > 0) verdict = 'needs-work'; const plan = { ...base, verdict, reasons }; - plan.aiAssisted = AI_DISCLOSURE.test(body); - if (plan.aiAssisted && !labels.has(AI_LABEL)) plan.labelsToAdd.push(AI_LABEL); - if (!plan.aiAssisted && labels.has(AI_LABEL)) plan.labelsToRemove.push(AI_LABEL); if (verdict === 'pass') { if (labels.has(GATE_LABEL)) plan.labelsToRemove.push(GATE_LABEL); @@ -326,8 +329,8 @@ function runCommentGate(repo, options) { if (options.apply) minimizeComment(comment.nodeId); } +// Exempt plans still reach here: they carry only disclosure-label actions. function applyIssuePlan(repo, plan) { - if (plan.exempt) return; if (plan.shouldReopen) reopenIssue(repo, plan.number); for (const label of plan.labelsToRemove) removeLabel(repo, plan.number, label); if (plan.labelsToAdd.length > 0) addLabels(repo, plan.number, plan.labelsToAdd); diff --git a/tests/github-issue-gate.test.mjs b/tests/github-issue-gate.test.mjs index 9ff8e7348..63774acff 100644 --- a/tests/github-issue-gate.test.mjs +++ b/tests/github-issue-gate.test.mjs @@ -238,6 +238,23 @@ describe('issue body gate', () => { assert.equal(member.exempt, true); }); + it('still reconciles the AI label for exempt authors', () => { + const disclosed = evaluateIssue(issue({ + authorLogin: 'pbakaus', + body: 'AI-assisted: yes\n\nquick note', + }), { templates }); + assert.equal(disclosed.exempt, true); + assert.deepEqual(disclosed.labelsToAdd, [AI_LABEL]); + + const undisclosed = evaluateIssue(issue({ + authorLogin: 'pbakaus', + body: 'quick note', + labels: [AI_LABEL], + }), { templates }); + assert.equal(undisclosed.exempt, true); + assert.deepEqual(undisclosed.labelsToRemove, [AI_LABEL]); + }); + it('warns a needs-work issue only once', () => { const plan = evaluateIssue(issue({ body: FILLED_BUG_BODY + '\n' + words(700),