Skip to content

Commit 34c1317

Browse files
authored
Refactor: Adapt GitHub automation and add benchmark suite (#1)
## Summary Adapt the repository automation to current ModularityKit.Mutator layout and add dedicated benchmark suite for the mutation engine. ## Added - Repo specific label definitions for abstractions, runtime, examples, tests, benchmark, documentation, architecture, ci, and performance - Labeler rules for the current folder layout under `src/`, `Examples/`, `Benchmarks/`, `Tests/`, `Docs/`, and `.github/` - Local Python helper for release asset uploads under `scripts/releases/` - BenchmarkDotNet project, benchmark entrypoint, and benchmark README for the mutation engine ## Result Repository automation now matches the current ModularityKit.Mutator structure, and the benchmark suite is available as separate trackable project. ## Testing - [x] `python3 -B -c "import ast, pathlib; ast.parse(pathlib.Path('scripts/releases/upload_release_assets.py').read_text())"` - [x] `git status --short --branch` ## Linked Issues - None ## Checklist - [x] PR is focused and does not include unrelated cleanup - [x] New behavior follows the existing project structure
2 parents 4259fd1 + 510dc67 commit 34c1317

18 files changed

Lines changed: 811 additions & 0 deletions

.github/config/auto-assign.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
addAssignees: author
2+
3+
addReviewers: false
4+
5+
skipKeywords:
6+
- wip
7+
- draft

.github/config/labeler.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
abstractions:
2+
- changed-files:
3+
- any-glob-to-any-file:
4+
- src/Abstractions/**
5+
6+
runtime:
7+
- changed-files:
8+
- any-glob-to-any-file:
9+
- src/Runtime/**
10+
11+
examples:
12+
- changed-files:
13+
- any-glob-to-any-file:
14+
- Examples/**
15+
16+
tests:
17+
- changed-files:
18+
- any-glob-to-any-file:
19+
- Tests/**
20+
- '**/*Tests.cs'
21+
22+
benchmark:
23+
- changed-files:
24+
- any-glob-to-any-file:
25+
- Benchmarks/**
26+
27+
documentation:
28+
- changed-files:
29+
- any-glob-to-any-file:
30+
- Docs/**
31+
- '*.md'
32+
- readme.md
33+
34+
architecture:
35+
- changed-files:
36+
- any-glob-to-any-file:
37+
- Docs/Decision/**
38+
- .github/config/**
39+
40+
ci:
41+
- changed-files:
42+
- any-glob-to-any-file:
43+
- .github/**

.github/config/labels.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
- name: bug
2+
color: "d73a4a"
3+
description: Something is broken or incorrect
4+
5+
- name: enhancement
6+
color: "a2eeef"
7+
description: New functionality or behavior
8+
9+
- name: documentation
10+
color: "0075ca"
11+
description: Documentation updates and additions
12+
13+
- name: architecture
14+
color: "8B5E3C"
15+
description: Design, structure, and API-shape changes
16+
17+
- name: abstractions
18+
color: "5B4B8A"
19+
description: Public abstractions and contracts
20+
21+
- name: runtime
22+
color: "8A4F7D"
23+
description: Runtime implementation and execution flow
24+
25+
- name: examples
26+
color: "1d76db"
27+
description: Runnable examples and sample apps
28+
29+
- name: ci
30+
color: "5319e7"
31+
description: CI/CD and repository automation changes
32+
33+
- name: tests
34+
color: "fbca04"
35+
description: Test coverage and test changes
36+
37+
- name: performance
38+
color: "0e4d92"
39+
description: Performance improvements or regressions
40+
41+
- name: benchmark
42+
color: "5319e7"
43+
description: Benchmark coverage and performance measurement changes
44+
45+
- name: breaking-change
46+
color: "b60205"
47+
description: Introduces a breaking change
48+
49+
- name: skip-changelog
50+
color: "ededed"
51+
description: Exclude this change from release notes
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"LABEL": {
3+
"name": "",
4+
"color": "EEEEEE"
5+
},
6+
"CHECKS": {
7+
"regexp": "^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\\([a-z0-9-]+\\))?: .+$",
8+
"regexpFlags": "i",
9+
"ignoreLabels": [
10+
"State One",
11+
"State Two"
12+
],
13+
"alwaysPassCI": false
14+
},
15+
"MESSAGES": {
16+
"success": "PR title matches the required format.",
17+
"failure": "PR title must match: type(optional-scope): description",
18+
"notice": "Allowed types: feat, fix, docs, style, refactor, perf, test, build, ci, chore."
19+
}
20+
}

.github/workflows/labels-sync.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Labels Sync
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
paths:
7+
- .github/config/labels.yml
8+
- .github/workflows/labels-sync.yml
9+
workflow_dispatch:
10+
11+
permissions:
12+
issues: write
13+
contents: read
14+
15+
jobs:
16+
sync:
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v5
22+
23+
- name: Sync repository labels
24+
uses: EndBug/label-sync@v2
25+
with:
26+
config-file: .github/config/labels.yml
27+
delete-other-labels: true
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
name: Pull Request Automation
2+
3+
on:
4+
pull_request_target:
5+
types: [ opened, edited, synchronize, reopened, ready_for_review, labeled, unlabeled ]
6+
7+
permissions:
8+
contents: read
9+
issues: write
10+
pull-requests: write
11+
12+
jobs:
13+
assign-author:
14+
if: github.event.action == 'opened' || github.event.action == 'ready_for_review'
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Assign PR author
19+
uses: kentaro-m/auto-assign-action@v2.0.0
20+
with:
21+
repo-token: ${{ secrets.GITHUB_TOKEN }}
22+
configuration-path: .github/config/auto-assign.yml
23+
24+
title-check:
25+
runs-on: ubuntu-latest
26+
27+
steps:
28+
- name: Check PR title
29+
uses: thehanimo/pr-title-checker@v1.4.3
30+
with:
31+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32+
pass_on_octokit_error: false
33+
configuration_path: .github/config/pr-title-checker.json
34+
35+
labeler:
36+
runs-on: ubuntu-latest
37+
38+
steps:
39+
- name: Label changed files
40+
uses: actions/labeler@v6
41+
with:
42+
configuration-path: .github/config/labeler.yml
43+
sync-labels: true
44+
45+
performance-labeler:
46+
runs-on: ubuntu-latest
47+
48+
steps:
49+
- name: Apply Performance label from issues or PR text
50+
uses: actions/github-script@v7
51+
with:
52+
github-token: ${{ secrets.GITHUB_TOKEN }}
53+
script: |
54+
const { owner, repo } = context.repo;
55+
const pr = context.payload.pull_request;
56+
const text = `${pr.title}\n${pr.body || ''}`.toLowerCase();
57+
const keywordMatch = /(perf|performance|benchmark|optimiz|allocation|memory pressure|throughput|latency)/i.test(text);
58+
const issueNumbers = new Set();
59+
60+
for (const match of text.matchAll(/(?:closes|fixes|related to)\s+#(\d+)/gi)) {
61+
issueNumbers.add(Number(match[1]));
62+
}
63+
64+
for (const match of text.matchAll(/#(\d+)/g)) {
65+
issueNumbers.add(Number(match[1]));
66+
}
67+
68+
let issueMatch = false;
69+
70+
for (const number of issueNumbers) {
71+
const issue = await github.rest.issues.get({ owner, repo, issue_number: number });
72+
const labels = issue.data.labels.map(label => label.name);
73+
74+
if (labels.includes('performance')) {
75+
issueMatch = true;
76+
break;
77+
}
78+
}
79+
80+
if (!keywordMatch && !issueMatch) {
81+
core.info('Performance label not applicable for this PR.');
82+
return;
83+
}
84+
85+
const currentLabels = pr.labels.map(label => label.name);
86+
if (currentLabels.includes('performance')) {
87+
core.info('Performance label already present.');
88+
return;
89+
}
90+
91+
await github.rest.issues.addLabels({
92+
owner,
93+
repo,
94+
issue_number: pr.number,
95+
labels: ['performance']
96+
});
97+
core.info('Performance label added.');

.github/workflows/pr-check.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: PR Check
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [ main, master, develop ]
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
configuration: [ Debug, Release ]
19+
20+
steps:
21+
- uses: actions/checkout@v5
22+
23+
- uses: actions/setup-dotnet@v5
24+
with:
25+
dotnet-version: 10.0.x
26+
27+
- name: Restore
28+
run: dotnet restore ModularityKit.Mutator.slnx
29+
30+
- name: Build ${{ matrix.configuration }}
31+
run: dotnet build ModularityKit.Mutator.slnx -c ${{ matrix.configuration }} --no-restore
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Publish Artifacts
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
package_version:
7+
description: "Optional package version, usually the release tag without the leading v."
8+
required: false
9+
type: string
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
package:
16+
name: Pack library
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v5
22+
23+
- name: Setup .NET
24+
uses: actions/setup-dotnet@v5
25+
with:
26+
dotnet-version: 10.0.x
27+
28+
- name: Restore
29+
run: dotnet restore src/ModularityKit.Mutator.csproj
30+
31+
- name: Resolve package version
32+
id: version
33+
env:
34+
PACKAGE_VERSION: ${{ inputs.package_version }}
35+
REF_NAME: ${{ github.ref_name }}
36+
run: |
37+
version="$PACKAGE_VERSION"
38+
if [ -z "$version" ]; then
39+
version="$REF_NAME"
40+
fi
41+
version="${version#v}"
42+
if ! printf '%s' "$version" | grep -Eq '^[0-9]+(\.[0-9]+){1,2}([-+][0-9A-Za-z.-]+)?$'; then
43+
version="0.1.0"
44+
fi
45+
echo "package_version=$version" >> "$GITHUB_OUTPUT"
46+
47+
- name: Pack package
48+
run: >
49+
dotnet pack src/ModularityKit.Mutator.csproj
50+
-c Release
51+
--no-restore
52+
-o nupkg
53+
-p:PackageVersion=${{ steps.version.outputs.package_version }}
54+
55+
- name: Upload package
56+
uses: actions/upload-artifact@v4
57+
with:
58+
name: ModularityKit.Mutator-nupkg
59+
path: nupkg/*.nupkg
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Publish Attested
2+
3+
on:
4+
workflow_dispatch:
5+
6+
permissions:
7+
contents: write
8+
id-token: write
9+
attestations: write
10+
artifact-metadata: write
11+
12+
jobs:
13+
publish:
14+
uses: ./.github/workflows/publish-artifacts.yml
15+
16+
release:
17+
name: Upload artifacts to draft release
18+
runs-on: ubuntu-latest
19+
needs: publish
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v5
24+
25+
- name: Download published artifacts
26+
uses: actions/download-artifact@v6
27+
with:
28+
pattern: ModularityKit.Mutator-nupkg
29+
path: dist
30+
merge-multiple: true
31+
32+
- name: Create or update draft release
33+
env:
34+
GITHUB_TOKEN: ${{ github.token }}
35+
REPOSITORY: ${{ github.repository }}
36+
DIST_DIR: dist
37+
FIND_DRAFT: "true"
38+
ENSURE_DRAFT: "true"
39+
FAIL_MESSAGE: "No draft release found. Release Drafter must create the draft before artifacts can be uploaded."
40+
ASSET_PATTERNS: |
41+
*
42+
run: python3 -m scripts.releases.upload_release_assets

0 commit comments

Comments
 (0)