Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions .github/workflows/first-time-contributor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: Welcome first-time contributors

on:
pull_request_target:
types: [opened]

permissions: {}

jobs:
agentscan:
if: >-
github.repository == 'nodejs/node' &&
(github.event.pull_request.author_association == 'FIRST_TIMER' ||
github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR')
runs-on: ubuntu-slim
permissions:
contents: read
outputs:
scan_outcome: ${{ steps.agentscan.outcome }}
classification: ${{ steps.agentscan.outputs.classification }}
community_flagged: ${{ steps.agentscan.outputs['community-flagged'] }}
steps:
- name: Scan contributor activity
id: agentscan
# The welcome should still be posted if this advisory scan fails.
continue-on-error: true
uses: MatteoGabriele/agentscan-action@98202262c925c508d4c1424b1dfbe17ee35b0c02 # v2.4.0
with:
github-token: ${{ github.token }}
mode: silent
scan-pull-requests: true
scan-issues: false
auto-close: false
honeypot: false

comment:
needs: agentscan
runs-on: ubuntu-slim
permissions:
pull-requests: write
steps:
- name: Welcome first-time contributor
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
AGENTSCAN_OUTCOME: ${{ needs.agentscan.outputs.scan_outcome }}
AGENTSCAN_CLASSIFICATION: ${{ needs.agentscan.outputs.classification }}
AGENTSCAN_COMMUNITY_FLAGGED: ${{ needs.agentscan.outputs.community_flagged }}
with:
github-token: ${{ github.token }}
script: |
const marker = '<!-- first-time-contributor-guidance -->';
const author = context.payload.pull_request.user.login;
const body = [
marker,
'',
'Welcome to Node.js, and thank you for your first contribution!',
'',
'Before review, please take a moment to read:',
'',
'* the [guide for first-time contributors](https://github.com/nodejs/node/blob/HEAD/doc/contributing/first-contributions.md)',
'* the [contribution and automation policies](https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md)',
'* the [pull request guide](https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md)',
'* the [AI use policy](https://github.com/nodejs/node/blob/HEAD/doc/contributing/ai-guidelines.md)',
'* the [Code of Conduct](https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md)',
'',
'Please make sure every commit is [signed off](https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#step-4-commit). For a first pull request, GitHub Actions require collaborator approval and Jenkins CI must be started by a collaborator or triager, so an initial wait is normal.',
];

const classification = process.env.AGENTSCAN_CLASSIFICATION;
// AgentScan also marks insufficient-data results as flagged, so use
// the specific classifications that warrant a maintainer caution.
const shouldCaution = process.env.AGENTSCAN_OUTCOME === 'success' &&
(process.env.AGENTSCAN_COMMUNITY_FLAGGED === 'true' ||
classification === 'mixed' || classification === 'automation');

if (shouldCaution) {
const profile = `https://agentscan.tools/user/${encodeURIComponent(author)}`;
body.push(
'',
'> [!CAUTION]',
'> **Maintainer note:** AgentScan found account activity patterns that may be consistent with automation.',
'> This is a heuristic, not proof that this pull request was opened by an agent or violates policy.',
'> AI-assisted contributions are permitted, but automated tooling must not open pull requests without',
'> advance approval, and contributors must personally understand, test, verify, and take responsibility',
'> for every submitted change. Review the [AgentScan analysis](' + profile + '),',
'> [AI use policy](https://github.com/nodejs/node/blob/HEAD/doc/contributing/ai-guidelines.md), and',
'> [automation policy](https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md#automation-and-bots)',
'> before acting.',
);
}

const { owner, repo } = context.repo;
const issue_number = context.payload.pull_request.number;
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number,
per_page: 100,
});
const existing = comments.find((comment) =>
comment.user?.login === 'github-actions[bot]' &&
comment.body?.includes(marker));
const comment = { owner, repo, body: body.join('\n') };

if (existing) {
await github.rest.issues.updateComment({
...comment,
comment_id: existing.id,
});
core.info(`Updated guidance comment on pull request #${issue_number}`);
} else {
await github.rest.issues.createComment({
...comment,
issue_number,
});
core.info(`Created guidance comment on pull request #${issue_number}`);
}
Loading