Skip to content

Commit 744650f

Browse files
kotlarmilosCopilot
andcommitted
Add agentic workflows for code review, CI scan, dependabot auto-merge, and stale issue cleanup
Adds four gh-aw workflows plus one plain finalize workflow: code-review on every PR, ci-scan periodic, needs-info-sweeper periodic, and dependabot-auto-merge reviewer + finalizer pair. Agent runs are read-only; writes are gated through capped, allowlisted safe-outputs. The finalize job is schedule-only and re-validates every merge condition before deferring to branch protection via gh pr merge --auto. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4c8b357 commit 744650f

10 files changed

Lines changed: 5860 additions & 4 deletions

.github/aw/actions-lock.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
"version": "v8",
66
"sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd"
77
},
8-
"github/gh-aw-actions/setup@v0.67.1": {
9-
"repo": "github/gh-aw-actions/setup",
10-
"version": "v0.67.1",
11-
"sha": "80471a493be8c528dd27daf73cd644242a7965e0"
8+
"actions/github-script@v9.0.0": {
9+
"repo": "actions/github-script",
10+
"version": "v9.0.0",
11+
"sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3"
1212
},
1313
"github/gh-aw/actions/setup@v0.58.0": {
1414
"repo": "github/gh-aw/actions/setup",

.github/workflows/ci-scan.agent.lock.yml

Lines changed: 1340 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/ci-scan.agent.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
description: |
3+
Daily. Scans recent CI failures across open PRs, identifies recurring
4+
failure signatures, and files Known Build Error issues so future PR
5+
CI flags them as ignorable. Read-only on code; never edits.
6+
7+
on:
8+
schedule: daily
9+
roles: [admin, maintain, write]
10+
11+
if: github.repository == 'dotnet/machinelearning'
12+
13+
timeout-minutes: 30
14+
15+
permissions: read-all
16+
17+
network:
18+
allowed:
19+
- defaults
20+
- github
21+
22+
tools:
23+
github:
24+
toolsets: [repos, pull_requests, actions, issues, search]
25+
bash: true
26+
27+
safe-outputs:
28+
noop:
29+
report-as-issue: false
30+
create-issue:
31+
title-prefix: "[ci-scan] "
32+
labels: [build, "Known Build Error", untriaged]
33+
max: 3
34+
add-comment:
35+
target: "*"
36+
max: 5
37+
hide-older-comments: true
38+
---
39+
40+
# CI Failure Scanner
41+
42+
Identify recurring CI failures across recent runs and file Known Build Error issues so PR CI can flag them as already-known.
43+
44+
## Hard rules
45+
46+
1. **Read-only on code.** Workflow does not push, rebase, or label PRs.
47+
2. **A failure is "recurring" if it has hit ≥ 3 distinct PRs in the last 14 days.** Below that bar, do nothing.
48+
3. **One issue per signature per run.** Before filing, search open issues for the same normalized signature (label `Known Build Error`). If found, comment with the new occurrences and stop.
49+
4. **Cap 3 new issues per run.** Force review cadence.
50+
5. **Never file an issue without a quoted failing log line.** No vague "tests fail sometimes".
51+
52+
## Process
53+
54+
1. List failed workflow runs in the last 14 days: `gh run list --status failure --created '>14d' --json databaseId,name,headBranch,event,conclusion,createdAt --limit 200`.
55+
2. For each failure, fetch the failing job logs: `gh run view <id> --log-failed`. Cap at 500 lines per job.
56+
3. Normalize each first-error line (strip paths, timestamps, run IDs, GUIDs).
57+
4. Group by normalized signature. Count distinct PRs affected per signature.
58+
5. For each signature with ≥ 3 distinct PRs:
59+
- Search for open issues with label `Known Build Error` matching the signature.
60+
- If found: comment with new occurrence count, list of recent PRs, latest run link.
61+
- If not found and budget allows: file a new issue using the template below.
62+
6. Stop after 3 new issues.
63+
64+
## Issue template
65+
66+
```
67+
[ci-scan] <short signature>
68+
69+
**Signature** (normalized):
70+
`<one line>`
71+
72+
**Failing line** (raw, one example):
73+
`<one line>`
74+
75+
**Affected PRs in last 14 days** (count: N):
76+
- #1234 (run: <url>)
77+
- #1235 (run: <url>)
78+
...
79+
80+
**Reproducer**: TBD by triage.
81+
82+
🤖 Filed by ci-scan agent.
83+
```

.github/workflows/code-review.agent.lock.yml

Lines changed: 1356 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
description: |
3+
First-pass review on every PR. Posts one comment with correctness,
4+
test coverage, and consistency-with-ML.NET-conventions analysis.
5+
Read-only otherwise.
6+
7+
on:
8+
pull_request:
9+
types: [opened, synchronize, ready_for_review]
10+
roles: [admin, maintain, write]
11+
12+
if: |
13+
github.repository == 'dotnet/machinelearning' &&
14+
github.event.pull_request.draft == false
15+
16+
timeout-minutes: 15
17+
18+
permissions: read-all
19+
20+
network:
21+
allowed:
22+
- defaults
23+
- github
24+
25+
tools:
26+
github:
27+
toolsets: [repos, pull_requests, search]
28+
web-fetch:
29+
30+
checkout:
31+
fetch-depth: 50
32+
33+
safe-outputs:
34+
noop:
35+
report-as-issue: false
36+
add-comment:
37+
target: "triggering"
38+
max: 1
39+
hide-older-comments: true
40+
---
41+
42+
# Code Review
43+
44+
Review PR #${{ github.event.pull_request.number }} and post one comment using the format below. Skip drafts.
45+
46+
## Hard rules
47+
48+
1. **Read-only.** No approvals, no labels, no commits.
49+
2. **One comment per run.** If previous comment is identical, `noop`.
50+
3. **High signal only.** Do not comment on style, formatting, or trivial naming. Only comment on bugs, missing null checks, incorrect numerical algorithms, breaking API changes, unmanaged-resource leaks, thread safety, and missing tests for new behavior.
51+
4. **Never claim certainty without proof.** If you suspect a bug, cite the file:line and quote the offending code.
52+
53+
## Scope
54+
55+
- `src/Microsoft.ML.*/**`: trainers, transforms, data. verify numerical correctness, IDataView contract, missing-value handling.
56+
- `src/Microsoft.ML.OnnxRuntime/**`, `src/Microsoft.ML.OnnxTransformer/**`: ONNX input/output schema, version compatibility.
57+
- `src/Microsoft.ML.Tokenizers/**`: encoding round-trips, BPE/WordPiece correctness.
58+
- `test/**`: missing assertions, missing edge cases (empty input, NaN, large input).
59+
- Public API changes: any new public type/member should have an `<api type="added" />` annotation in `eng/api-baselines/` or equivalent.
60+
61+
## Output format
62+
63+
```
64+
🤖 Code Review
65+
66+
### Critical
67+
- file.cs:42. <one-line bug description with quoted code>
68+
69+
### Suggestions
70+
- file.cs:88. <one-line non-blocking observation>
71+
72+
### Tests
73+
- <one line on test coverage of new behavior>
74+
75+
### Verdict
76+
<one of: looks good / changes requested / needs author response>
77+
```
78+
79+
If no critical findings, fewer than 2 suggestions, and tests look adequate, `noop` instead of posting an empty review.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
name: Dependabot Auto-Merge. Finalize
2+
3+
on:
4+
schedule:
5+
- cron: "*/30 * * * *"
6+
7+
permissions:
8+
contents: write
9+
pull-requests: write
10+
actions: write
11+
12+
concurrency:
13+
group: dependabot-auto-merge-finalize
14+
cancel-in-progress: false
15+
16+
jobs:
17+
finalize:
18+
if: github.repository == 'dotnet/machinelearning'
19+
runs-on: ubuntu-latest
20+
timeout-minutes: 15
21+
env:
22+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23+
REPO: ${{ github.repository }}
24+
WAIT_HOURS: "4"
25+
MAX_ATTEMPTS: "3"
26+
steps:
27+
- name: Process candidates
28+
run: |
29+
set -euo pipefail
30+
31+
mapfile -t prs < <(
32+
gh pr list \
33+
--repo "$REPO" \
34+
--label auto-merge-candidate \
35+
--state open \
36+
--json number,author,title,mergeable,updatedAt,headRefOid,isDraft \
37+
--jq '.[] | select(.isDraft | not) | @json'
38+
)
39+
40+
if [ "${#prs[@]}" -eq 0 ]; then
41+
echo "No candidates."
42+
exit 0
43+
fi
44+
45+
now=$(date -u +%s)
46+
47+
for json in "${prs[@]}"; do
48+
number=$(jq -r '.number' <<<"$json")
49+
author=$(jq -r '.author.login' <<<"$json")
50+
title=$(jq -r '.title' <<<"$json")
51+
mergeable=$(jq -r '.mergeable' <<<"$json")
52+
updated=$(jq -r '.updatedAt' <<<"$json")
53+
sha=$(jq -r '.headRefOid' <<<"$json")
54+
55+
echo "::group::PR #$number"
56+
57+
case "$author" in
58+
dependabot|dependabot[bot]) ;;
59+
*) echo "author=$author not dependabot, skipping."; echo "::endgroup::"; continue;;
60+
esac
61+
62+
case "$title" in
63+
"Bump "*) ;;
64+
*) echo "title not dependabot shape, skipping."; echo "::endgroup::"; continue;;
65+
esac
66+
67+
if [ "$mergeable" != "MERGEABLE" ]; then
68+
echo "mergeable=$mergeable, skipping."
69+
echo "::endgroup::"
70+
continue
71+
fi
72+
73+
checks_json=$(gh pr checks "$number" --repo "$REPO" --required --json name,state,link)
74+
failing=$(jq '[.[] | select(.state == "FAILURE" or .state == "CANCELLED")] | length' <<<"$checks_json")
75+
pending=$(jq '[.[] | select(.state == "PENDING" or .state == "IN_PROGRESS" or .state == "QUEUED")] | length' <<<"$checks_json")
76+
77+
if [ "$pending" -gt 0 ] && [ "$failing" -eq 0 ]; then
78+
echo "$pending required check(s) still running, will retry next cycle."
79+
echo "::endgroup::"
80+
continue
81+
fi
82+
83+
if [ "$failing" -gt 0 ]; then
84+
mapfile -t failed_runs < <(
85+
gh run list \
86+
--repo "$REPO" \
87+
--commit "$sha" \
88+
--status failure \
89+
--limit 50 \
90+
--json databaseId,name,attempt,event \
91+
--jq '.[] | select(.event == "pull_request" or .event == "push") | @json'
92+
)
93+
94+
rerun_count=0
95+
giveup=0
96+
for run_json in "${failed_runs[@]}"; do
97+
run_id=$(jq -r '.databaseId' <<<"$run_json")
98+
run_name=$(jq -r '.name' <<<"$run_json")
99+
attempt=$(jq -r '.attempt' <<<"$run_json")
100+
101+
if [ "$attempt" -ge "$MAX_ATTEMPTS" ]; then
102+
echo "run $run_id ($run_name): attempt=$attempt >= $MAX_ATTEMPTS, giving up."
103+
giveup=1
104+
continue
105+
fi
106+
107+
echo "run $run_id ($run_name): attempt=$attempt, rerunning failed jobs."
108+
if gh run rerun "$run_id" --failed --repo "$REPO"; then
109+
rerun_count=$((rerun_count + 1))
110+
else
111+
echo "run $run_id: rerun call failed, treating as giveup."
112+
giveup=1
113+
fi
114+
done
115+
116+
if [ "$giveup" -eq 1 ]; then
117+
echo "Attempt cap reached. Removing auto-merge-candidate; a human needs to look."
118+
gh pr edit "$number" --repo "$REPO" --remove-label auto-merge-candidate
119+
gh pr comment "$number" --repo "$REPO" --body \
120+
"🤖 Dependabot auto-merge: required CI is red after $MAX_ATTEMPTS attempts. Removing \`auto-merge-candidate\`. A human needs to triage. Re-add the label after the underlying issue is resolved."
121+
elif [ "$rerun_count" -gt 0 ]; then
122+
echo "Triggered $rerun_count rerun(s); will re-evaluate next cycle."
123+
fi
124+
echo "::endgroup::"
125+
continue
126+
fi
127+
128+
updated_epoch=$(date -u -d "$updated" +%s 2>/dev/null || date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$updated" +%s)
129+
age_hours=$(( (now - updated_epoch) / 3600 ))
130+
if [ "$age_hours" -lt "$WAIT_HOURS" ]; then
131+
echo "age ${age_hours}h < ${WAIT_HOURS}h required, skipping."
132+
echo "::endgroup::"
133+
continue
134+
fi
135+
136+
echo "All gates green and minimum age reached. Merging."
137+
gh pr merge "$number" \
138+
--repo "$REPO" \
139+
--squash \
140+
--delete-branch \
141+
--auto
142+
echo "::endgroup::"
143+
done

0 commit comments

Comments
 (0)