-
-
Notifications
You must be signed in to change notification settings - Fork 5k
179 lines (156 loc) · 7.21 KB
/
ai-policy.yml
File metadata and controls
179 lines (156 loc) · 7.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: AI Policy
on:
pull_request:
types: [opened, synchronize, reopened]
branches: [master, main]
permissions:
contents: read
# Required to add the "AI assisted" label via `gh pr edit --add-label`
pull-requests: write
# Required to create the "AI assisted" label via the REST labels endpoint
# (labels are an issues-scoped resource in the GitHub API)
issues: write
concurrency:
group: ai-policy-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
check-ai-trailers:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- name: Collect PR commit messages
id: collect
env:
BASE_REF: ${{ github.base_ref }}
run: |
set -euo pipefail
git fetch origin "${BASE_REF}"
MERGE_BASE=$(git merge-base "origin/${BASE_REF}" HEAD)
git log --format="%B" "${MERGE_BASE}..HEAD" > /tmp/pr_commits.txt
echo "--- PR commit messages ---"
cat /tmp/pr_commits.txt
echo "--------------------------"
- name: Define shared agent detection patterns
run: |
set -euo pipefail
# Email addresses known to be used by coding agents.
# These should never appear in Signed-off-by because the DCO can only be attested by a human.
EMAIL_PATTERN="copilot@github\.com\
|noreply@anthropic\.com\
|devin@cognition\.ai\
|devin@cognition-labs\.com\
|aider@aider\.chat\
|noreply@aider\.chat\
|codex@openai\.com\
|cursor@anysphere\.com\
|windsurf@codeium\.com\
|codeium@codeium\.com\
|amazon-q@amazon\.com\
|codewhisperer@amazon\.com\
|gemini-code-assist@google\.com\
|openhands@all-hands\.dev\
|swe-agent@princeton\.edu"
# Strip embedded whitespace (used above only for readability)
EMAIL_PATTERN=$(echo "$EMAIL_PATTERN" | tr -d ' \n')
echo "AGENT_EMAIL_PATTERN=${EMAIL_PATTERN}" >> "$GITHUB_ENV"
# Display-name prefixes used by known coding agents (shared by Signed-off-by and Co-Authored-By checks)
# shellcheck disable=SC2016
echo 'AGENT_NAMES=GitHub Copilot|Claude( [A-Za-z0-9. -]+)?|Devin( AI)?|aider( \(.*\))?|OpenAI Codex|Cursor( AI)?|Windsurf|Amazon Q|CodeWhisperer|Gemini Code Assist|OpenHands|SWE-agent|AutoCodeRover|Tabnine' >> "$GITHUB_ENV"
- name: Check for AI-assistant / Assisted-by trailers
id: ai_trailers
run: |
set -euo pipefail
AI_ASSISTED=false
if grep -qiE '^(AI-assistant|Assisted-by):' /tmp/pr_commits.txt; then
AI_ASSISTED=true
echo "Found AI-assistant/Assisted-by trailer(s):"
grep -iE '^(AI-assistant|Assisted-by):' /tmp/pr_commits.txt
fi
echo "ai_assisted=${AI_ASSISTED}" >> "$GITHUB_OUTPUT"
- name: Check for coding-agent Signed-off-by trailers
id: agent_signoff
run: |
set -euo pipefail
EMAIL_HITS=$(grep -iE "^Signed-off-by:.*<(${AGENT_EMAIL_PATTERN})>" /tmp/pr_commits.txt 2>/dev/null || true)
NAME_HITS=$(grep -iE "^Signed-off-by: *(${AGENT_NAMES}) *[<(]" /tmp/pr_commits.txt 2>/dev/null || true)
AGENT_LINES=$(printf '%s\n%s' "$EMAIL_HITS" "$NAME_HITS" | sort -u | sed '/^[[:space:]]*$/d')
AGENT_SIGNOFF=false
if [ -n "$AGENT_LINES" ]; then
AGENT_SIGNOFF=true
fi
echo "agent_signoff=${AGENT_SIGNOFF}" >> "$GITHUB_OUTPUT"
{
echo "agent_lines<<AGENT_EOF"
echo "${AGENT_LINES}"
echo "AGENT_EOF"
} >> "$GITHUB_OUTPUT"
- name: Check for coding-agent Co-Authored-By trailers
id: co_authored
run: |
set -euo pipefail
EMAIL_HITS=$(grep -iE "^Co-Authored-By:.*<(${AGENT_EMAIL_PATTERN})>" /tmp/pr_commits.txt 2>/dev/null || true)
NAME_HITS=$(grep -iE "^Co-Authored-By: *(${AGENT_NAMES}) *[<(]" /tmp/pr_commits.txt 2>/dev/null || true)
CO_AUTHORED=false
if [ -n "$EMAIL_HITS" ] || [ -n "$NAME_HITS" ]; then
CO_AUTHORED=true
echo "Found coding-agent Co-Authored-By trailer(s):"
printf '%s\n%s' "$EMAIL_HITS" "$NAME_HITS" | sort -u | sed '/^[[:space:]]*$/d'
fi
echo "co_authored=${CO_AUTHORED}" >> "$GITHUB_OUTPUT"
- name: Create 'AI assisted' label if absent
if: steps.ai_trailers.outputs.ai_assisted == 'true' || steps.agent_signoff.outputs.agent_signoff == 'true' || steps.co_authored.outputs.co_authored == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api "repos/${{ github.repository }}/labels" \
--method POST \
-f name="AI assisted" \
-f color="d93f0b" \
-f description="This PR contains AI-assisted commits" \
2>/dev/null || true
- name: Label PR as AI assisted
if: steps.ai_trailers.outputs.ai_assisted == 'true' || steps.agent_signoff.outputs.agent_signoff == 'true' || steps.co_authored.outputs.co_authored == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh pr edit "${{ github.event.pull_request.number }}" \
--repo "${{ github.repository }}" \
--add-label "AI assisted"
echo "Added 'AI assisted' label to PR #${{ github.event.pull_request.number }}"
- name: Fail on coding-agent Signed-off-by
if: steps.agent_signoff.outputs.agent_signoff == 'true'
env:
AGENT_LINES: ${{ steps.agent_signoff.outputs.agent_lines }}
AGENTS_MD_URL: https://github.com/${{ github.repository }}/blob/${{ github.base_ref }}/AGENTS.md
run: |
echo "::error title=Coding-agent sign-off detected::A Signed-off-by trailer from a known coding agent was found in one or more commits."
echo ""
echo "Offending trailer(s):"
echo "${AGENT_LINES}"
echo ""
echo "The 'Signed-off-by' trailer represents the Developer Certificate of Origin (DCO)"
echo "and must only be attested by a human contributor."
echo "Please amend the affected commit(s) to remove the coding-agent sign-off"
echo "and replace it with an 'Assisted-by' trailer, for example:"
echo ""
echo " Assisted-by: Claude Code:claude-sonnet-4-6"
echo ""
echo "References:"
echo " • AGENTS.md (this repository)"
echo " ${AGENTS_MD_URL}"
echo " • AI Contribution Policy"
echo " https://github.com/nextcloud/.github/blob/master/AI_POLICY.md"
echo " • Contribution Guidelines"
echo " https://github.com/nextcloud/.github/blob/master/CONTRIBUTING.md"
exit 1