From 2f40d4dacfcfd2ceea1add39143d1a55f7727281 Mon Sep 17 00:00:00 2001 From: beanbean9339 Date: Wed, 9 Sep 2026 16:44:06 -0400 Subject: [PATCH 1/2] fix: identify Copilot as automated contributor in contributor filtering --- src/services/githubImporterContributors.js | 4 +++ tests/services/githubImporter.test.js | 31 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/services/githubImporterContributors.js b/src/services/githubImporterContributors.js index cb1733a..074fcdb 100644 --- a/src/services/githubImporterContributors.js +++ b/src/services/githubImporterContributors.js @@ -95,6 +95,10 @@ function isAutomatedContributor(contributor, profile, cleanString) { return true; } + if (login.includes('copilot') || profileName.includes('copilot')) { + return true; + } + if (isAutomatedContributorIdentity(login, cleanString)) { return true; } diff --git a/tests/services/githubImporter.test.js b/tests/services/githubImporter.test.js index 229da7d..f81f204 100644 --- a/tests/services/githubImporter.test.js +++ b/tests/services/githubImporter.test.js @@ -1084,6 +1084,9 @@ test('importGithubMetadata excludes AI bot co-authors and contributor accounts w return Response.json([ { login: 'claude-code', type: 'User' }, { login: 'copilot-swe-agent', type: 'User' }, + { login: 'automation-COPILOT', type: 'User' }, + { login: 'profile-helper', type: 'User' }, + { login: 'alice-example', type: 'User' }, ]); } @@ -1105,14 +1108,40 @@ test('importGithubMetadata excludes AI bot co-authors and contributor accounts w }); } + if (value.endsWith('/users/profile-helper')) { + return Response.json({ + login: 'profile-helper', + type: 'User', + name: 'Mixed CoPiLoT Name', + html_url: 'https://github.com/profile-helper', + }); + } + + if (value.endsWith('/users/alice-example')) { + return Response.json({ + login: 'alice-example', + type: 'User', + name: 'Alice Example', + html_url: 'https://github.com/alice-example', + }); + } + if (value.endsWith('/users/claude-code/social_accounts') || value.endsWith('/users/copilot-swe-agent/social_accounts')) { return Response.json([]); } + if (value.endsWith('/users/profile-helper/social_accounts') || value.endsWith('/users/alice-example/social_accounts')) { + return Response.json([]); + } + if (value === 'https://github.com/claude-code' || value === 'https://github.com/copilot-swe-agent') { return new Response('', { status: 200, headers: { 'Content-Type': 'text/html' } }); } + if (value === 'https://github.com/profile-helper' || value === 'https://github.com/alice-example') { + return new Response('', { status: 200, headers: { 'Content-Type': 'text/html' } }); + } + throw new Error(`Unexpected fetch URL: ${value}`); }; @@ -1126,6 +1155,8 @@ test('importGithubMetadata excludes AI bot co-authors and contributor accounts w assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Claude' && author.familyNames === 'Fable'), false); assert.equal(result.metadata.authors.some((author) => author.givenNames === 'GitHub' && author.familyNames === 'Copilot'), false); assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Claude' && author.familyNames === 'Code'), false); + assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Mixed' && author.familyNames === 'CoPiLoT Name'), false); + assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Alice' && author.familyNames === 'Example'), true); } finally { globalThis.fetch = originalFetch; } From 0d8151d8784c38dbfa7526ba0552fda4b3eb1742 Mon Sep 17 00:00:00 2001 From: beanbean9339 Date: Mon, 14 Sep 2026 11:07:22 -0400 Subject: [PATCH 2/2] fix: enhance contributor filtering to exclude additional AI bot identities --- src/services/githubImporterContributors.js | 17 +++++--- tests/services/githubImporter.test.js | 45 +++++++++++++++++++++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/services/githubImporterContributors.js b/src/services/githubImporterContributors.js index 074fcdb..3c63d18 100644 --- a/src/services/githubImporterContributors.js +++ b/src/services/githubImporterContributors.js @@ -21,6 +21,10 @@ function isAutomatedContributorIdentity(value, cleanString) { return false; } + if (normalized.includes('copilot') || normalized.includes('gemini') || normalized.includes('chatgpt') || normalized.includes('openai') || normalized.includes('cursor')) { + return true; + } + if (normalized.includes('[bot]') || normalized.endsWith('-bot') || normalized.startsWith('bot-') || normalized.includes('-bot')) { return true; } @@ -35,6 +39,8 @@ function isAutomatedContributorIdentity(value, cleanString) { 'chatgpt', 'gpt', 'openai', + 'gemini', + 'cursor', 'assistant', 'bot', ]); @@ -59,7 +65,7 @@ function isAutomatedContributorIdentity(value, cleanString) { return true; } - if ((first === 'claude' || first === 'copilot' || first === 'swe') && secondTokenIsAutomationKeyword) { + if ((first === 'claude' || first === 'copilot' || first === 'gemini' || first === 'cursor' || first === 'swe') && secondTokenIsAutomationKeyword) { return true; } @@ -81,6 +87,11 @@ function isAutomatedContributorIdentity(value, cleanString) { 'dependabot', 'chatgpt', 'openai', + 'gemini code', + 'gemini agent', + 'gemini cli', + 'cursor agent', + 'cursor cli', 'ai assistant', ].some((phrase) => combinedPhrase.includes(phrase)); } @@ -95,10 +106,6 @@ function isAutomatedContributor(contributor, profile, cleanString) { return true; } - if (login.includes('copilot') || profileName.includes('copilot')) { - return true; - } - if (isAutomatedContributorIdentity(login, cleanString)) { return true; } diff --git a/tests/services/githubImporter.test.js b/tests/services/githubImporter.test.js index f81f204..55df6c5 100644 --- a/tests/services/githubImporter.test.js +++ b/tests/services/githubImporter.test.js @@ -1066,7 +1066,7 @@ test('importGithubMetadata excludes AI bot co-authors and contributor accounts w { commit: { committer: { date: '2025-01-02T00:00:00Z' }, - message: 'Implement feature\n\nCo-authored-by: Net \nCo-authored-by: GitHub Copilot \nCo-authored-by: Claude Fable 5 ', + message: 'Implement feature\n\nCo-authored-by: Net \nCo-authored-by: GitHub Copilot \nCo-authored-by: Copilot Coding Agent \nCo-authored-by: Claude Fable 5 \nCo-authored-by: Gemini Code Assist \nCo-authored-by: Cursor Agent \nCo-authored-by: ChatGPT ', }, }, ]); @@ -1085,6 +1085,8 @@ test('importGithubMetadata excludes AI bot co-authors and contributor accounts w { login: 'claude-code', type: 'User' }, { login: 'copilot-swe-agent', type: 'User' }, { login: 'automation-COPILOT', type: 'User' }, + { login: 'gemini-code-assist', type: 'User' }, + { login: 'cursor-agent', type: 'User' }, { login: 'profile-helper', type: 'User' }, { login: 'alice-example', type: 'User' }, ]); @@ -1108,6 +1110,24 @@ test('importGithubMetadata excludes AI bot co-authors and contributor accounts w }); } + if (value.endsWith('/users/gemini-code-assist')) { + return Response.json({ + login: 'gemini-code-assist', + type: 'User', + name: 'Gemini Code Assist', + html_url: 'https://github.com/gemini-code-assist', + }); + } + + if (value.endsWith('/users/cursor-agent')) { + return Response.json({ + login: 'cursor-agent', + type: 'User', + name: 'Cursor Agent', + html_url: 'https://github.com/cursor-agent', + }); + } + if (value.endsWith('/users/profile-helper')) { return Response.json({ login: 'profile-helper', @@ -1130,6 +1150,14 @@ test('importGithubMetadata excludes AI bot co-authors and contributor accounts w return Response.json([]); } + if (value.endsWith('/users/gemini-code-assist/social_accounts')) { + return Response.json([]); + } + + if (value.endsWith('/users/cursor-agent/social_accounts')) { + return Response.json([]); + } + if (value.endsWith('/users/profile-helper/social_accounts') || value.endsWith('/users/alice-example/social_accounts')) { return Response.json([]); } @@ -1138,24 +1166,37 @@ test('importGithubMetadata excludes AI bot co-authors and contributor accounts w return new Response('', { status: 200, headers: { 'Content-Type': 'text/html' } }); } + if (value === 'https://github.com/gemini-code-assist') { + return new Response('', { status: 200, headers: { 'Content-Type': 'text/html' } }); + } + if (value === 'https://github.com/profile-helper' || value === 'https://github.com/alice-example') { return new Response('', { status: 200, headers: { 'Content-Type': 'text/html' } }); } + if (value === 'https://github.com/cursor-agent') { + return new Response('', { status: 200, headers: { 'Content-Type': 'text/html' } }); + } + throw new Error(`Unexpected fetch URL: ${value}`); }; try { const result = await importGithubMetadata('https://github.com/test-owner/test-repo', { - contributorFallbackLimit: 5, + contributorFallbackLimit: 7, }); assert.equal(result.errors.length, 0); assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Net' && !author.familyNames), true); assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Claude' && author.familyNames === 'Fable'), false); assert.equal(result.metadata.authors.some((author) => author.givenNames === 'GitHub' && author.familyNames === 'Copilot'), false); + assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Copilot' && author.familyNames === 'Coding Agent'), false); assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Claude' && author.familyNames === 'Code'), false); assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Mixed' && author.familyNames === 'CoPiLoT Name'), false); + assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Gemini' && author.familyNames === 'Code Assist'), false); + assert.equal(result.metadata.authors.some((author) => /gemini/i.test(author.givenNames ?? '') || /gemini/i.test(author.familyNames ?? '')), false); + assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Cursor' && author.familyNames === 'Agent'), false); + assert.equal(result.metadata.authors.some((author) => /chatgpt/i.test(author.givenNames ?? '') || /chatgpt/i.test(author.familyNames ?? '')), false); assert.equal(result.metadata.authors.some((author) => author.givenNames === 'Alice' && author.familyNames === 'Example'), true); } finally { globalThis.fetch = originalFetch;