Skip to content

[codex] Improve author-action issue labeling #12

[codex] Improve author-action issue labeling

[codex] Improve author-action issue labeling #12

name: Evaluate Submission
on:
issues:
types: [edited, labeled]
issue_comment:
types: [created]
jobs:
evaluate:
if: github.event_name == 'issues' && github.event.action == 'labeled' && github.event.label.name == 'suggestion'
runs-on: ubuntu-latest
permissions:
actions: write
contents: read
issues: write
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- run: pip install pyyaml
- name: Extract GitHub repo from issue body
id: extract
uses: actions/github-script@v9
with:
script: |
const body = context.payload.issue.body || '';
function normalizeGitHubRepo(value) {
if (!value) return '';
const trimmed = value.trim();
const slugMatch = trimmed.match(/^(?:https?:\/\/github\.com\/)?([a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+?)(?:\.git|\/)?$/);
return slugMatch ? slugMatch[1] : '';
}
// Try the structured "github" field first (from issue template)
const ghFieldMatch = body.match(/### GitHub Repository\s*\n\s*\n?\s*([\s\S]*?)(?=\n###|$)/);
const ghRepo = normalizeGitHubRepo(ghFieldMatch ? ghFieldMatch[1] : '');
if (ghRepo) {
core.setOutput('repo', ghRepo);
return;
}
// Fallback: extract from any GitHub URL in the body
const urlMatch = body.match(/github\.com\/([a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+)/);
if (urlMatch) {
core.setOutput('repo', urlMatch[1]);
return;
}
core.setOutput('repo', '');
- name: Run evaluation
if: steps.extract.outputs.repo != ''
id: eval
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
REPORT=$(python3 scripts/evaluate_entry.py --data-dir data/ "${{ steps.extract.outputs.repo }}" 2>&1) || true
echo "$REPORT" > /tmp/eval-report.md
python3 scripts/evaluate_entry.py --data-dir data/ --json "${{ steps.extract.outputs.repo }}" 2>/dev/null > /tmp/eval-result.json || true
REC=$(python3 -c "import sys,json; print(json.load(open('/tmp/eval-result.json')).get('recommendation','error'))" 2>/dev/null || echo "error")
ARCHIVED=$(python3 -c "import sys,json; print(str(json.load(open('/tmp/eval-result.json')).get('archived',False)).lower())" 2>/dev/null || echo "false")
echo "recommendation=$REC" >> "$GITHUB_OUTPUT"
echo "archived=$ARCHIVED" >> "$GITHUB_OUTPUT"
- name: Post evaluation comment
if: steps.extract.outputs.repo != ''
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
const report = fs.readFileSync('/tmp/eval-report.md', 'utf8');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: report
});
- name: Apply label and handle outcome
if: steps.extract.outputs.repo != ''
uses: actions/github-script@v9
with:
script: |
const rec = '${{ steps.eval.outputs.recommendation }}';
const archived = '${{ steps.eval.outputs.archived }}' === 'true';
const issue = context.issue.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
// Archived rejects close immediately; 0-score rejects stay in incubator queue
let label;
if (rec === 'reject' && archived) {
label = 'rejected';
} else {
const labelMap = {
'accept': 'accepted',
'likely_accept': 'accepted',
'incubator': 'incubator',
'reject': 'incubator',
'duplicate': 'duplicate',
'error': 'needs-review',
};
label = labelMap[rec] || 'needs-review';
}
await github.rest.issues.addLabels({ owner, repo, issue_number: issue, labels: [label] });
if (label === 'accepted') {
await github.rest.actions.createWorkflowDispatch({
owner,
repo,
workflow_id: 'add-accepted-entry.yml',
ref: context.payload.repository.default_branch,
inputs: {
issue_number: String(issue),
},
});
}
// Auto-close duplicates and archived rejects
if (rec === 'duplicate' || (rec === 'reject' && archived)) {
await github.rest.issues.update({
owner, repo, issue_number: issue,
state: 'closed',
state_reason: 'not_planned',
});
}
- name: Post fallback comment (no repo found)
if: steps.extract.outputs.repo == ''
uses: actions/github-script@v9
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: [
'👋 Thanks for the suggestion!',
'',
'I couldn\'t find a GitHub repository in this issue. Could you add the **GitHub Repository** field ',
'(e.g., `owner/repo`) so I can run an automated evaluation?',
'',
'If the project isn\'t on GitHub, please reply confirming that this should be reviewed as a non-GitHub resource and include any public repository or documentation link maintainers should use.',
'',
'This issue is labeled `question` while waiting for author input. If there is no update for about two weeks, automation will mark it stale; after another two weeks without activity, it may close automatically.',
].join('\n')
});
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['needs-review', 'question']
});
clear-author-question:
if: |
(github.event_name == 'issues' && github.event.action == 'edited') ||
(github.event_name == 'issue_comment' && !github.event.issue.pull_request)
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Clear author-action labels after author update
uses: actions/github-script@v9
with:
script: |
const issue = context.payload.issue;
const labels = (issue.labels || []).map(label => typeof label === 'string' ? label : label.name);
if (!labels.includes('suggestion') || !labels.includes('question')) {
return;
}
const author = issue.user.login;
const sender = context.payload.sender.login;
if (sender !== author) {
return;
}
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue_number = issue.number;
async function removeLabel(label) {
try {
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: label });
} catch (error) {
if (error.status !== 404) {
throw error;
}
}
}
await removeLabel('question');
await removeLabel('stale');