diff --git a/.all-contributorsrc b/.all-contributorsrc index acb7a344a..bfc9002be 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -69,13 +69,6 @@ "profile": "https://github.com/didier-durand", "contributions": [] }, - { - "login": "18jeffreyma", - "name": "Jeffrey Ma", - "avatar_url": "https://avatars.githubusercontent.com/18jeffreyma", - "profile": "https://github.com/18jeffreyma", - "contributions": [] - }, { "login": "V0XNIHILI", "name": "Douwe den Blanken", @@ -83,6 +76,13 @@ "profile": "https://github.com/V0XNIHILI", "contributions": [] }, + { + "login": "18jeffreyma", + "name": "Jeffrey Ma", + "avatar_url": "https://avatars.githubusercontent.com/18jeffreyma", + "profile": "https://github.com/18jeffreyma", + "contributions": [] + }, { "login": "shanzehbatool", "name": "shanzehbatool", @@ -202,13 +202,6 @@ "profile": "https://github.com/ma3mool", "contributions": [] }, - { - "login": "DivyaAmirtharaj", - "name": "Divya Amirtharaj", - "avatar_url": "https://avatars.githubusercontent.com/DivyaAmirtharaj", - "profile": "https://github.com/DivyaAmirtharaj", - "contributions": [] - }, { "login": "srivatsankrishnan", "name": "Srivatsan Krishnan", @@ -217,17 +210,10 @@ "contributions": [] }, { - "login": "James-QiuHaoran", - "name": "Haoran Qiu", - "avatar_url": "https://avatars.githubusercontent.com/James-QiuHaoran", - "profile": "https://github.com/James-QiuHaoran", - "contributions": [] - }, - { - "login": "aptl26", - "name": "Aghyad Deeb", - "avatar_url": "https://avatars.githubusercontent.com/aptl26", - "profile": "https://github.com/aptl26", + "login": "DivyaAmirtharaj", + "name": "Divya Amirtharaj", + "avatar_url": "https://avatars.githubusercontent.com/DivyaAmirtharaj", + "profile": "https://github.com/DivyaAmirtharaj", "contributions": [] }, { @@ -237,6 +223,20 @@ "profile": "https://github.com/arnaumarin", "contributions": [] }, + { + "login": "aptl26", + "name": "Aghyad Deeb", + "avatar_url": "https://avatars.githubusercontent.com/aptl26", + "profile": "https://github.com/aptl26", + "contributions": [] + }, + { + "login": "James-QiuHaoran", + "name": "Haoran Qiu", + "avatar_url": "https://avatars.githubusercontent.com/James-QiuHaoran", + "profile": "https://github.com/James-QiuHaoran", + "contributions": [] + }, { "login": "VThuong99", "name": "Thuong Duong", @@ -244,6 +244,13 @@ "profile": "https://github.com/VThuong99", "contributions": [] }, + { + "login": "Ekhao", + "name": "Emil Njor", + "avatar_url": "https://avatars.githubusercontent.com/Ekhao", + "profile": "https://github.com/Ekhao", + "contributions": [] + }, { "login": "AditiR-42", "name": "Aditi Raju", @@ -287,10 +294,10 @@ "contributions": [] }, { - "login": "Ekhao", - "name": "Emil Njor", - "avatar_url": "https://avatars.githubusercontent.com/Ekhao", - "profile": "https://github.com/Ekhao", + "login": "leo47007", + "name": "Yu-Shun Hsiao", + "avatar_url": "https://avatars.githubusercontent.com/leo47007", + "profile": "https://github.com/leo47007", "contributions": [] }, { @@ -300,13 +307,6 @@ "profile": "https://github.com/jaywonchung", "contributions": [] }, - { - "login": "leo47007", - "name": "Yu-Shun Hsiao", - "avatar_url": "https://avatars.githubusercontent.com/leo47007", - "profile": "https://github.com/leo47007", - "contributions": [] - }, { "login": "BaeHenryS", "name": "Henry Bae", @@ -321,6 +321,13 @@ "profile": "https://github.com/eimlav", "contributions": [] }, + { + "login": "AndreaMattiaGaravagno", + "name": "AndreaMattiaGaravagno", + "avatar_url": "https://avatars.githubusercontent.com/AndreaMattiaGaravagno", + "profile": "https://github.com/AndreaMattiaGaravagno", + "contributions": [] + }, { "login": "pongtr", "name": "Pong Trairatvorakul", @@ -343,10 +350,10 @@ "contributions": [] }, { - "login": "ShvetankPrakash", - "name": "Shvetank Prakash", - "avatar_url": "https://avatars.githubusercontent.com/ShvetankPrakash", - "profile": "https://github.com/ShvetankPrakash", + "login": "marcozennaro", + "name": "Marco Zennaro", + "avatar_url": "https://avatars.githubusercontent.com/marcozennaro", + "profile": "https://github.com/marcozennaro", "contributions": [] }, { @@ -357,10 +364,10 @@ "contributions": [] }, { - "login": "AndreaMattiaGaravagno", - "name": "AndreaMattiaGaravagno", - "avatar_url": "https://avatars.githubusercontent.com/AndreaMattiaGaravagno", - "profile": "https://github.com/AndreaMattiaGaravagno", + "login": "ShvetankPrakash", + "name": "Shvetank Prakash", + "avatar_url": "https://avatars.githubusercontent.com/ShvetankPrakash", + "profile": "https://github.com/ShvetankPrakash", "contributions": [] }, { @@ -371,10 +378,10 @@ "contributions": [] }, { - "login": "marcozennaro", - "name": "Marco Zennaro", - "avatar_url": "https://avatars.githubusercontent.com/marcozennaro", - "profile": "https://github.com/marcozennaro", + "login": "jzhou1318", + "name": "Jennifer Zhou", + "avatar_url": "https://avatars.githubusercontent.com/jzhou1318", + "profile": "https://github.com/jzhou1318", "contributions": [] }, { @@ -384,13 +391,6 @@ "profile": "https://github.com/euranofshin", "contributions": [] }, - { - "login": "jzhou1318", - "name": "Jennifer Zhou", - "avatar_url": "https://avatars.githubusercontent.com/jzhou1318", - "profile": "https://github.com/jzhou1318", - "contributions": [] - }, { "login": "jianqingdu", "name": "jianqingdu", @@ -405,6 +405,13 @@ "profile": "https://github.com/harvard-edge/cs249r_book/graphs/contributors", "contributions": [] }, + { + "login": "gnodipac886", + "name": "gnodipac886", + "avatar_url": "https://avatars.githubusercontent.com/gnodipac886", + "profile": "https://github.com/gnodipac886", + "contributions": [] + }, { "login": "Tess314", "name": "Tess314", @@ -426,6 +433,13 @@ "profile": "https://github.com/taunoe", "contributions": [] }, + { + "login": "FinAminToastCrunch", + "name": "Fin Amin", + "avatar_url": "https://avatars.githubusercontent.com/FinAminToastCrunch", + "profile": "https://github.com/FinAminToastCrunch", + "contributions": [] + }, { "login": "BunningsWarehouseOfficial", "name": "Kristian Rado\u0161", @@ -440,13 +454,6 @@ "profile": "https://github.com/BrunoScaglione", "contributions": [] }, - { - "login": "Gjain234", - "name": "Gauri Jain", - "avatar_url": "https://avatars.githubusercontent.com/Gjain234", - "profile": "https://github.com/Gjain234", - "contributions": [] - }, { "login": "Allen-Kuang", "name": "Allen-Kuang", @@ -455,10 +462,10 @@ "contributions": [] }, { - "login": "gnodipac886", - "name": "gnodipac886", - "avatar_url": "https://avatars.githubusercontent.com/gnodipac886", - "profile": "https://github.com/gnodipac886", + "login": "Gjain234", + "name": "Gauri Jain", + "avatar_url": "https://avatars.githubusercontent.com/Gjain234", + "profile": "https://github.com/Gjain234", "contributions": [] }, { @@ -468,13 +475,6 @@ "profile": "https://github.com/serco425", "contributions": [] }, - { - "login": "TheHiddenLayer", - "name": "TheHiddenLayer", - "avatar_url": "https://avatars.githubusercontent.com/TheHiddenLayer", - "profile": "https://github.com/TheHiddenLayer", - "contributions": [] - }, { "login": "alex-oesterling", "name": "Alex Oesterling", @@ -483,10 +483,10 @@ "contributions": [] }, { - "login": "FinAminToastCrunch", - "name": "Fin Amin", - "avatar_url": "https://avatars.githubusercontent.com/FinAminToastCrunch", - "profile": "https://github.com/FinAminToastCrunch", + "login": "TheHiddenLayer", + "name": "TheHiddenLayer", + "avatar_url": "https://avatars.githubusercontent.com/TheHiddenLayer", + "profile": "https://github.com/TheHiddenLayer", "contributions": [] }, { @@ -496,6 +496,13 @@ "profile": "https://github.com/KarthikDani", "contributions": [] }, + { + "login": "Jahnic-kb", + "name": "Jahnic Beck", + "avatar_url": "https://avatars.githubusercontent.com/Jahnic-kb", + "profile": "https://github.com/Jahnic-kb", + "contributions": [] + }, { "login": "RinZ27", "name": "Rin", @@ -504,10 +511,10 @@ "contributions": [] }, { - "login": "BravoBaldo", - "name": "Baldassarre Cesarano", - "avatar_url": "https://avatars.githubusercontent.com/BravoBaldo", - "profile": "https://github.com/BravoBaldo", + "login": "XaicuL", + "name": "JEON HYUNJUN(Luciano)", + "avatar_url": "https://avatars.githubusercontent.com/XaicuL", + "profile": "https://github.com/XaicuL", "contributions": [] }, { @@ -518,10 +525,17 @@ "contributions": [] }, { - "login": "Jahnic-kb", - "name": "Jahnic Beck", - "avatar_url": "https://avatars.githubusercontent.com/Jahnic-kb", - "profile": "https://github.com/Jahnic-kb", + "login": "BravoBaldo", + "name": "Baldassarre Cesarano", + "avatar_url": "https://avatars.githubusercontent.com/BravoBaldo", + "profile": "https://github.com/BravoBaldo", + "contributions": [] + }, + { + "login": "abigailswallow", + "name": "abigailswallow", + "avatar_url": "https://avatars.githubusercontent.com/abigailswallow", + "profile": "https://github.com/abigailswallow", "contributions": [] }, { @@ -531,13 +545,6 @@ "profile": "https://github.com/YangZhou1997", "contributions": [] }, - { - "login": "XaicuL", - "name": "JEON HYUNJUN(Luciano)", - "avatar_url": "https://avatars.githubusercontent.com/XaicuL", - "profile": "https://github.com/XaicuL", - "contributions": [] - }, { "login": "cursoragent", "name": "Cursor Agent", @@ -574,10 +581,10 @@ "contributions": [] }, { - "login": "abigailswallow", - "name": "abigailswallow", - "avatar_url": "https://avatars.githubusercontent.com/abigailswallow", - "profile": "https://github.com/abigailswallow", + "login": "adil-mubashir-ch", + "name": "Adil Mubashir Chaudhry", + "avatar_url": "https://avatars.githubusercontent.com/adil-mubashir-ch", + "profile": "https://github.com/adil-mubashir-ch", "contributions": [] }, { diff --git a/.github/workflows/all-contributors-add.yml b/.github/workflows/all-contributors-add.yml index b172344ff..174fd6ca5 100644 --- a/.github/workflows/all-contributors-add.yml +++ b/.github/workflows/all-contributors-add.yml @@ -3,9 +3,8 @@ # ============================================================================= # Automatically adds contributors when someone comments with @all-contributors. # -# Uses Ollama LLM to parse natural language and extract: -# - GitHub username (with or without @) -# - Contribution type(s) +# Username is extracted DETERMINISTICALLY via regex from @mentions. +# Uses Ollama LLM ONLY to classify contribution type(s) from natural language. # # Project detection is DETERMINISTIC (not LLM-guessed): # - PR file paths: tinytorch/ → tinytorch, book/ → book, kits/ → kits, labs/ → labs @@ -61,7 +60,7 @@ jobs: # ===================================================================== # STEP 1: Extract trigger line + detect project from PR files # ===================================================================== - - name: Extract trigger line and detect project + - name: Extract trigger line, username, and detect project id: extract uses: actions/github-script@v7 env: @@ -171,23 +170,39 @@ jobs: // If still null → projectSource stays 'unknown', handled downstream console.log(`Final project: ${project || 'NONE'} (source: ${projectSource})`); + // ============================================================= + // USERNAME EXTRACTION (deterministic — regex, not LLM) + // ============================================================= + const mentions = triggerLine.match(/@([\w][\w-]*)/g); + const cleanMentions = mentions + ? mentions.map(m => m.replace(/^@/, '')).filter(m => m !== 'all-contributors') + : []; + + const username = cleanMentions.length > 0 ? cleanMentions[0] : ''; + console.log(`Username from @mention: "${username}"`); + + if (!username) { + console.log('No username @mention found in trigger line'); + } + core.setOutput('should_run', 'true'); core.setOutput('trigger_line', triggerLine); + core.setOutput('username', username); core.setOutput('issue_context', issueContext); core.setOutput('project', project || ''); core.setOutput('project_source', projectSource); # ===================================================================== - # STEP 2: LLM parses username + contribution types (NOT project) + # STEP 2: LLM classifies contribution types ONLY (username is from regex) # ===================================================================== - - name: Parse with LLM - if: steps.extract.outputs.should_run == 'true' + - name: Classify contribution types with LLM + if: steps.extract.outputs.should_run == 'true' && steps.extract.outputs.username != '' uses: ai-action/ollama-action@v2 id: llm with: model: ${{ env.LLM_MODEL }} prompt: | - Parse this contributor recognition comment. Extract ONLY the username and contribution types. + Classify the contribution type(s) from this comment. COMMENT: ${{ steps.extract.outputs.trigger_line }} @@ -201,41 +216,40 @@ jobs: - test: Tested features, verified fixes, QA testing - tool: Built tools, scripts, automation, CLI utilities - Return ONLY a JSON object with exactly these 2 fields: + Return ONLY a JSON object with exactly this field: { - "username": "", "types": [""] } RULES: - - username: The GitHub username WITHOUT the @ symbol. Ignore @all-contributors itself. - types: Array of one or more contribution types from the list above. - - Do NOT include a "project" field. Project is detected separately. + - Do NOT include username or project fields. Those are detected separately. EXAMPLES: Input: "@all-contributors @jane-doe fixed typos in the documentation" - Output: {"username": "jane-doe", "types": ["doc"]} - - Input: "@all-contributors please add @ngbolin for Doc in Tinytorch" - Output: {"username": "ngbolin", "types": ["doc"]} + Output: {"types": ["doc"]} Input: "@all-contributors @dev42 implemented the new feature and wrote tests" - Output: {"username": "dev42", "types": ["code", "test"]} + Output: {"types": ["code", "test"]} Input: "@all-contributors please add @user123 for code" - Output: {"username": "user123", "types": ["code"]} + Output: {"types": ["code"]} + + Input: "@all-contributors @reviewer99 gave feedback on the PR" + Output: {"types": ["review"]} Return ONLY the JSON object, no explanation or other text. # ===================================================================== - # STEP 3: Parse LLM JSON + combine with deterministic project + # STEP 3: Parse LLM types + combine with deterministic username & project # ===================================================================== - - name: Parse LLM response and validate + - name: Validate and combine results if: steps.extract.outputs.should_run == 'true' id: parse uses: actions/github-script@v7 env: - LLM_RESPONSE: ${{ steps.llm.outputs.response }} + LLM_RESPONSE: ${{ steps.llm.outputs.response || '' }} + USERNAME: ${{ steps.extract.outputs.username }} TRIGGER_LINE: ${{ steps.extract.outputs.trigger_line }} PROJECT: ${{ steps.extract.outputs.project }} PROJECT_SOURCE: ${{ steps.extract.outputs.project_source }} @@ -244,28 +258,30 @@ jobs: with: script: | const response = process.env.LLM_RESPONSE || ''; + const username = process.env.USERNAME || ''; const triggerLine = process.env.TRIGGER_LINE || ''; const project = process.env.PROJECT || ''; const projectSource = process.env.PROJECT_SOURCE || ''; const validTypes = process.env.CONTRIBUTION_TYPES.split(','); const validProjects = process.env.PROJECTS.split(','); + console.log('Username (from regex):', username); console.log('LLM response:', response); console.log('Deterministic project:', project || 'NONE', `(source: ${projectSource})`); - let username = null; - let types = []; + // --- Validate username (extracted deterministically in Step 1) --- + if (!username) { + core.setOutput('success', 'false'); + core.setOutput('error', 'no_username'); + return; + } - // --- Parse LLM JSON response --- + // --- Parse contribution types from LLM response --- + let types = []; try { const jsonMatch = response.match(/\{[\s\S]*?\}/); if (jsonMatch) { const parsed = JSON.parse(jsonMatch[0]); - - if (parsed.username && typeof parsed.username === 'string') { - username = parsed.username.replace(/^@/, ''); - } - if (parsed.types && Array.isArray(parsed.types)) { types = parsed.types .map(t => t.toLowerCase().trim()) @@ -276,22 +292,6 @@ jobs: console.log('Failed to parse LLM JSON:', e.message); } - // --- Fallback: extract username from @mentions --- - if (!username) { - const mentions = triggerLine.match(/@([\w][\w-]*)/g); - if (mentions && mentions.length > 1) { - username = mentions[1].replace(/^@/, ''); - console.log('Username from @mention fallback:', username); - } - } - - // --- Validate username --- - if (!username) { - core.setOutput('success', 'false'); - core.setOutput('error', 'no_username'); - return; - } - // --- Validate types --- if (types.length === 0) { core.setOutput('success', 'false'); @@ -312,7 +312,7 @@ jobs: } // --- All good --- - console.log('✅ Final result:', { username, types, project, projectSource }); + console.log('Final result:', { username, types, project, projectSource }); core.setOutput('success', 'true'); core.setOutput('username', username);