-
Notifications
You must be signed in to change notification settings - Fork 75
210 lines (185 loc) · 9.33 KB
/
Copy pathprelint.yml
File metadata and controls
210 lines (185 loc) · 9.33 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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
###############################################################################
# PR Lint gate — Commit Message + PR Title + Branch Name + Issue Link
#
# Jobs:
# commit-lint — requires checkout; runs independently
# pr-checks — all github-script checks consolidated into one runner
# (PR title, branch name, linked issue)
###############################################################################
name: 🔍 PR Lint
on:
pull_request:
types: [opened, synchronize, reopened, edited]
branches: [main, 'feature/**', 'release/**']
permissions:
contents: read
pull-requests: read
jobs:
# =========================================================================
# Job 1: Commit Message Lint
# =========================================================================
commit-lint:
name: 📝 Commit Message Lint
runs-on: anolisa-k8s-general-ci-x64
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Lint commit messages
uses: wagoid/commitlint-github-action@v6
with:
configFile: .github.qkg1.topmitlint.config.json
# =========================================================================
# Job 2: PR Checks (title + branch name + linked issue + merge hint)
# All steps run with continue-on-error so every check is always reported
# in one pass — contributors see all warnings at once, not one at a time.
# None of these checks block the PR (warnings only); only commit scope
# enforcement (commitlint) is a hard error.
# =========================================================================
pr-checks:
name: 🔍 PR Checks
runs-on: anolisa-k8s-general-ci-x64
steps:
# -----------------------------------------------------------------------
# Step 1: PR Title Validation (warning only)
# -----------------------------------------------------------------------
- name: 📋 Validate PR title
continue-on-error: true
uses: actions/github-script@v7
with:
script: |
const title = context.payload.pull_request.title;
console.log(`PR Title: "${title}"`);
// Conventional Commits pattern: type(scope): description
// Optional ! for breaking changes
const pattern = /^(feat|fix|refactor|perf|docs|chore|test|ci|build|style|revert)(\(.+\))!?: .+/;
if (!pattern.test(title)) {
core.warning(
`PR title does not follow Conventional Commits format.\n` +
`Expected: type(scope): description\n` +
`Examples: feat(cosh): add json output\n` +
` fix(sec-core): handle sandbox escape\n` +
`Valid types: feat, fix, refactor, perf, docs, chore, test, ci, build, style, revert\n` +
`Valid scopes: cosh, sec-core, skill, sight, tokenless, ckpt, memory, anolisa, skillfs, ktuner, deps, ci, docs, chore\n` +
`See CONTRIBUTING.md or AGENT.md for details.`
);
return;
}
// Validate scope (also accept legacy long names for forward compatibility)
const scopeMatch = title.match(/^\w+(\([^)]+\))/);
if (scopeMatch) {
const scope = scopeMatch[1].slice(1, -1);
const validScopes = [
'cosh', 'sec-core', 'skill', 'sight', 'tokenless', 'ckpt', 'memory', 'anolisa', 'skillfs', 'ktuner',
'agent-sec-core', 'agentsight', 'os-skills', 'ws-ckpt', 'agent-memory', // legacy aliases
'deps', 'ci', 'docs', 'chore'
];
if (!validScopes.includes(scope)) {
core.warning(
`Scope "${scope}" in PR title is not in the recommended list.\n` +
`Recommended scopes: ${validScopes.join(', ')}\n` +
`See CONTRIBUTING.md for the scope-to-path mapping.`
);
return;
}
}
console.log('✅ PR title format is valid');
# -----------------------------------------------------------------------
# Step 2: Branch Name Validation (warning only, non-blocking for forks)
# -----------------------------------------------------------------------
- name: 🌿 Validate branch name
continue-on-error: true
uses: actions/github-script@v7
with:
script: |
const branch = context.payload.pull_request.head.ref;
const baseBranch = context.payload.pull_request.base.ref;
console.log(`Head branch: "${branch}"`);
console.log(`Base branch: "${baseBranch}"`);
// Whitelist: these branches are always allowed
const whitelist = [
/^main$/,
/^dependabot\/.+/,
/^renovate\/.+/,
/^revert-.+/,
/^chore\/changelog-.+/, // created by changelog.yml workflow
];
for (const pattern of whitelist) {
if (pattern.test(branch)) {
console.log(`✅ Branch "${branch}" is whitelisted`);
return;
}
}
// Valid scopes — short names preferred; legacy long names accepted for forward compatibility
const validScopes = ['cosh', 'sec-core', 'skill', 'sight', 'tokenless', 'ckpt', 'memory', 'anolisa', 'skillfs', 'ktuner', 'agent-sec-core', 'agentsight', 'os-skills', 'ws-ckpt', 'agent-memory', 'ci', 'docs', 'deps'];
const scopePattern = validScopes.join('|');
// Valid branch patterns per development spec
const validPatterns = [
// feature/<scope>/<desc>
new RegExp(`^feature/(${scopePattern})/[a-z0-9][a-z0-9._-]*$`),
// feature/<scope>/<feature-name>/<task> (collaborative track)
new RegExp(`^feature/(${scopePattern})/[a-z0-9][a-z0-9._-]*/[a-z0-9][a-z0-9._-]*$`),
// fix/<scope>/<desc>
new RegExp(`^fix/(${scopePattern})/[a-z0-9][a-z0-9._-]*$`),
// release/<scope>/vX.Y
new RegExp(`^release/(${scopePattern})/v\\d+\\.\\d+$`),
// hotfix/<scope>/<desc>
new RegExp(`^hotfix/(${scopePattern})/[a-z0-9][a-z0-9._-]*$`),
];
const isValid = validPatterns.some(p => p.test(branch));
if (!isValid) {
core.warning(
`Branch "${branch}" does not match the recommended naming convention (non-blocking).\n` +
`Recommended formats:\n` +
` feature/<scope>/<short-desc> e.g. feature/cosh/json-output\n` +
` fix/<scope>/<short-desc> e.g. fix/sec-core/sandbox-escape\n` +
` hotfix/<scope>/<short-desc> e.g. hotfix/skill/broken-load\n` +
`Valid scopes: ${validScopes.join(', ')}\n` +
`Fork contributors may use any branch name freely.`
);
return;
}
console.log('✅ Branch name is valid');
# -----------------------------------------------------------------------
# Step 3: Linked Issue Check (warning only)
# -----------------------------------------------------------------------
- name: 🔗 Check linked issue
continue-on-error: true
uses: actions/github-script@v7
with:
script: |
const body = context.payload.pull_request.body || '';
// Accept standard GitHub closing keywords linking to an issue number
const closingKeywords = /\b(closes|fixes|resolves|close|fix|resolve)\s+#\d+/i;
// Accept explicit exemption: no-issue: <reason>
const noIssueExemption = /no-issue\s*:/i;
if (closingKeywords.test(body)) {
const match = body.match(/(?:closes|fixes|resolves|close|fix|resolve)\s+#(\d+)/i);
console.log(`✅ PR is linked to issue #${match[1]}`);
return;
}
if (noIssueExemption.test(body)) {
const exemptMatch = body.match(/no-issue\s*:\s*(.+)/i);
console.log(`✅ Issue link exempted: ${exemptMatch?.[1]?.trim() || '(no reason given)'}`);
return;
}
core.warning(
`This PR does not reference a linked issue.\n` +
`Add one of the following to your PR description:\n` +
` closes #<number> / fixes #<number> / resolves #<number>\n` +
`If there is genuinely no applicable issue, add:\n` +
` no-issue: <brief reason>\n` +
`Create an issue first: https://github.com/alibaba/anolisa/issues/new`
);
# -----------------------------------------------------------------------
# Step 4: Summary — always runs, prints consolidated status
# -----------------------------------------------------------------------
- name: 📊 PR Checks Summary
if: always()
uses: actions/github-script@v7
with:
script: |
console.log('=== PR Checks Summary ===');
console.log('All checks completed. Any warnings above are non-blocking suggestions.');
console.log('The only hard error that blocks merging is a missing commit scope.');
console.log('See CONTRIBUTING.md or AGENT.md for guidance on commit messages and PR conventions.');