Files
dingtalk-workspace-cli/.github/workflows/ai-behavior-check.yml
T
修雨 474ce88d47 fix(ci): harden CLI smoke and Schema compatibility gates (#629)
* fix(ci): harden PR gate enforcement

* fix(schema): allow compatible positional evolution
2026-07-16 09:59:21 +08:00

96 lines
3.4 KiB
YAML

name: AI Behavior Check
on:
pull_request_target:
types: [opened, synchronize, reopened, labeled, unlabeled]
permissions:
contents: read
pull-requests: read
statuses: write
jobs:
ai-behavior-check:
name: AI Behavior Policy Evaluator
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
# Deliberately do not check out or execute pull-request code here.
# pull_request_target keeps this policy anchored to the base branch.
- name: Check AI-generated PR boundaries
uses: actions/github-script@v7
with:
script: |
const sha = context.payload.pull_request.head.sha;
const setStatus = (state, description) =>
github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha,
state,
context: 'AI Behavior Check',
description,
});
await setStatus('pending', 'Evaluating AI-generated PR boundaries');
const labels = context.payload.pull_request.labels.map(({ name }) => name);
if (!labels.includes('ai-generated')) {
await setStatus('success', 'Not labeled ai-generated');
core.notice('Not an ai-generated PR; no AI-only policy applied.');
return;
}
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
per_page: 100,
});
const maxChangedFiles = 30;
if (files.length > maxChangedFiles) {
await setStatus(
'failure',
`Changes ${files.length} files; limit is ${maxChangedFiles}`
);
core.setFailed(
`AI-generated PR changes ${files.length} files; limit is ${maxChangedFiles}.`
);
return;
}
const isProtectedPath = (filename) =>
typeof filename === 'string' &&
(
filename.startsWith('.github/workflows/') ||
filename.startsWith('scripts/policy/') ||
filename.startsWith('scripts/release/') ||
filename === 'test/fixtures/cli-interface-baseline.txt' ||
filename === '.goreleaser.yaml' ||
filename === 'Makefile'
);
const protectedPaths = [...new Set(
files
.flatMap(({ filename, previous_filename }) => [filename, previous_filename])
.filter(isProtectedPath)
)];
if (protectedPaths.length > 0) {
await setStatus('failure', 'Modifies protected release/CI infrastructure');
core.setFailed(
'AI-generated PR modifies protected release/CI infrastructure:\n' +
protectedPaths.map((filename) => ` - ${filename}`).join('\n') +
'\nSplit these changes into a human-owned PR with explicit review.'
);
return;
}
await setStatus(
'success',
`Passed with ${files.length} changed files (limit ${maxChangedFiles})`
);
core.notice(
`AI behavior check passed (${files.length} changed files; limit ${maxChangedFiles}).`
);