merge: bring workflow fix from main (regex username extraction)

This commit is contained in:
Vijay Janapa Reddi
2026-02-19 12:05:33 -05:00
2 changed files with 149 additions and 142 deletions

View File

@@ -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": []
},
{

View File

@@ -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": "<github-username-without-@>",
"types": ["<contribution-type>"]
}
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);