Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 25 additions & 17 deletions .agents/skills/merge-skia-update/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,32 +46,39 @@ Only after the maintainer sees the exact selected pair, ask them to confirm:
If they do not confirm, stop. Do not independently re-audit reviews, labels,
milestones, CI, packages, DEPS, bindings, or release notes.

## 2. Preserve the previous release line when targeting main
## 2. Preserve the previous release line for milestone bumps

Run the script with the branch names already resolved by the AI:

```powershell
pwsh .agents/skills/merge-skia-update/scripts/Prepare-SkiaReleaseBranches.ps1 `
-SkiaSharpBaseBranch <parent-base> `
-SkiaSharpHeadBranch <parent-head> `
-SkiaBaseBranch <native-base>
```

The script determines the release action from the parent base branch:

- `main`: reads the committed SkiaSharp package version from
`scripts/VERSIONS.txt` and derives `release/A.B.x` for the current line;
- `main`: compares the committed `chrome_milestone` on the parent base and head;
when the milestone increases, it reads the base's committed SkiaSharp package
version from `scripts/VERSIONS.txt` and derives `release/A.B.x` for the
previous line;
- `main` with no milestone change: reports a same-milestone sync and exits
without creating refs;
- `release/A.B.x`: reports a servicing sync and exits without creating refs.

The script always defaults to a dry run. When targeting `main`, it reads the
current SkiaSharp base tip and the exact mono/skia commit referenced by its
`externals/skia` gitlink. It requires the supplied mono/skia base branch to
point at that commit and preflights the derived release branch in both
The script always defaults to a dry run. For a milestone bump targeting `main`,
it reads the current SkiaSharp base tip and the exact mono/skia commit referenced
by its `externals/skia` gitlink. It requires the supplied mono/skia base branch
to point at that commit and preflights the derived release branch in both
repositories.

Show the output and obtain confirmation. Rerun with the same inputs plus
`-Push`. The script checks the source and destination refs again, creates the
mono/skia release branch first, then the mono/SkiaSharp release branch, and
verifies both. It never moves an existing branch.
When the script reports a milestone bump, show the output and obtain
confirmation. Rerun with the same inputs plus `-Push`. The script checks the
source and destination refs again, creates the mono/skia release branch first,
then the mono/SkiaSharp release branch, and verifies both. It never moves an
existing branch. When it reports a same-milestone or servicing sync, continue
without a push confirmation.

## 3. Prepare the mono/skia merge

Expand Down Expand Up @@ -136,8 +143,8 @@ and body to the maintainer.

Tell the maintainer to merge it manually using the repository's normal merge
method. Do not wait for another PR CI run: the repin script proved that the
merged mono/skia tree is identical to the reviewed PR tree. Main CI validates
the resulting merge.
merged mono/skia tree is identical to the reviewed PR tree. CI on the resolved
parent base branch validates the resulting merge.

Stop. Continue only after the maintainer says mono/SkiaSharp is merged.

Expand All @@ -146,12 +153,12 @@ Stop. Continue only after the maintainer says mono/SkiaSharp is merged.
Confirm:

- both PRs are merged;
- mono/SkiaSharp main points its gitlink and cgmanifest at the actual mono/skia
merge commit;
- main CI has started for the resulting SkiaSharp commit.
- the resolved mono/SkiaSharp parent base branch points its gitlink and
cgmanifest at the actual mono/skia merge commit;
- CI has started on that parent base branch for the resulting SkiaSharp commit.

Report both merged SHAs and the preserved release branch, if created. Do not
wait for main CI to finish.
wait for the parent base branch CI to finish.

## Stop conditions

Expand All @@ -166,6 +173,7 @@ Stop when:
- a supplied native base branch does not match the parent base gitlink;
- an existing release branch points at a different SHA;
- a release source changes between dry run and `-Push`;
- the parent head milestone regresses;
- mono/skia was not merged with a two-parent merge commit;
- that merge does not contain the parent PR's reviewed native commit;
- the merged and reviewed native trees differ;
Expand Down
23 changes: 18 additions & 5 deletions .agents/skills/merge-skia-update/evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"expectations": [
"Does not repeat the deep review.",
"Verifies that the two supplied PRs reciprocally link to each other.",
"Passes AI-resolved branch names to Prepare-SkiaReleaseBranches.ps1 without -Push before applying.",
"Passes AI-resolved parent base/head and native base branch names to Prepare-SkiaReleaseBranches.ps1 without -Push before applying.",
"Invokes pr-commit-message for mono/skia.",
"Does not merge either PR."
]
Expand Down Expand Up @@ -41,16 +41,29 @@
{
"id": 3,
"prompt": "The M160 bump is approved. Create the previous release branches before I merge.",
"expected_output": "The AI passes the resolved main and native base branches; the script derives release/A.B.x from the current SkiaSharp package version committed on main, then uses the parent base tip and its exact externals/skia gitlink to create matching branches, native first, after a confirmed dry run.",
"expected_output": "The AI passes the resolved parent base/head and native base branches; the script verifies that the parent milestone increases, derives release/A.B.x from the current SkiaSharp package version committed on main, then uses the parent base tip and its exact externals/skia gitlink to create matching branches, native first, after a confirmed dry run.",
"files": [],
"expectations": [
"The AI resolves the base branches but does not calculate or pass the release line.",
"The AI resolves the parent base/head and native base branches but does not calculate or pass the release line.",
"The script confirms that chrome_milestone increases from the parent base to the parent head.",
"The script derives release/A.B.x from scripts/VERSIONS.txt on the exact parent base commit.",
"Uses the native SHA referenced by the parent base.",
"Preflights both destinations before either write.",
"Never moves an existing release branch."
]
},
{
"id": 11,
"prompt": "This approved Skia update targets main but stays on the current milestone. Prepare the merge.",
"expected_output": "Passes the resolved parent base/head and native base branches to the preparation script, which compares their committed chrome_milestone values, reports a same-milestone sync, creates no release refs, and continues to the native merge-message handoff.",
"files": [],
"expectations": [
"Does not create a release branch for a same-milestone main update.",
"Does not ask for a release-branch push confirmation.",
"Generates the native merge message.",
"Does not merge the native PR."
]
},
{
"id": 4,
"prompt": "This is an approved same-milestone servicing sync. Prepare the merge.",
Expand Down Expand Up @@ -115,12 +128,12 @@
{
"id": 8,
"prompt": "The repin is pushed. Wait for all PR CI before giving me the SkiaSharp merge message.",
"expected_output": "Generates the SkiaSharp merge message without waiting for another PR CI run because tree equality proved the native content unchanged; it notes that main CI validates the resulting merge.",
"expected_output": "Generates the SkiaSharp merge message without waiting for another PR CI run because tree equality proved the native content unchanged; it notes that CI on the resolved parent base branch validates the resulting merge.",
"files": [],
"expectations": [
"Does not rerun the deep review.",
"Does not wait for refreshed PR CI.",
"Generates the parent merge message.",
"Generates the parent merge message and refers to CI on the resolved parent base branch rather than assuming main.",
"Leaves the parent merge to the maintainer."
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ param(
[Parameter(Mandatory)]
[string] $SkiaSharpBaseBranch,

[Parameter(Mandatory)]
[string] $SkiaSharpHeadBranch,

[Parameter(Mandatory)]
[string] $SkiaBaseBranch,

Expand Down Expand Up @@ -35,6 +38,29 @@ function Get-GitHubRepository {
return $match.Groups['repository'].Value
}

function Get-SkiaMilestone {
param([string] $Commit)

$manifestText = git show "${Commit}:cgmanifest.json" | Out-String
$manifest = $manifestText | ConvertFrom-Json
$registrations = @(
$manifest.registrations | Where-Object {
$_.component.type -eq 'other' -and
$_.component.other.name -eq 'skia' -and
$null -ne $_.chrome_milestone
}
)
if ($registrations.Count -ne 1) {
throw "Cannot derive the Skia milestone from cgmanifest.json at $Commit."
}

$milestone = [int] $registrations[0].chrome_milestone
if ($milestone -le 0) {
throw "Invalid Skia milestone '$($registrations[0].chrome_milestone)' in cgmanifest.json at $Commit."
}
return $milestone
}

function Assert-Destination {
param(
[string] $Repository,
Expand All @@ -59,6 +85,7 @@ function New-RemoteBranch {
$repoRoot = git rev-parse --show-toplevel
Set-Location $repoRoot
git check-ref-format --branch $SkiaSharpBaseBranch | Out-Null
git check-ref-format --branch $SkiaSharpHeadBranch | Out-Null
git check-ref-format --branch $SkiaBaseBranch | Out-Null

$parentUrl = git remote get-url origin
Expand All @@ -75,6 +102,20 @@ if ($SkiaSharpBaseBranch -ne 'main') {
throw "SkiaSharp base branch must be main or release/A.B.x; got $SkiaSharpBaseBranch."
}

git fetch --no-tags origin "+refs/heads/${SkiaSharpHeadBranch}:refs/remotes/origin/${SkiaSharpHeadBranch}" | Out-Null
$parentHeadSha = git rev-parse "refs/remotes/origin/${SkiaSharpHeadBranch}^{commit}"

$baseMilestone = Get-SkiaMilestone $parentBaseSha
$headMilestone = Get-SkiaMilestone $parentHeadSha
if ($headMilestone -lt $baseMilestone) {
throw "Skia milestone regresses from m$baseMilestone on $SkiaSharpBaseBranch to m$headMilestone on $SkiaSharpHeadBranch."
}
if ($headMilestone -eq $baseMilestone) {
Write-Host "Same-milestone sync: m$baseMilestone -> m$headMilestone."
Write-Host 'No release branches are needed.'
exit 0
}

$versions = (git show "${parentBaseSha}:scripts/VERSIONS.txt" | Out-String).Trim()
$versionMatch = [regex]::Match($versions, '(?m)^SkiaSharp\s+nuget\s+(?<major>\d+)\.(?<minor>\d+)\.\d+(?:[-+]\S+)?\s*$')
if (-not $versionMatch.Success) {
Expand Down Expand Up @@ -104,6 +145,7 @@ if ($nativeBranchSha -ne $nativeBaseSha) {
$nativeExisting = Assert-Destination $nativeRepository $nativeUrl $ReleaseBranch $nativeBaseSha
$parentExisting = Assert-Destination $parentRepository $parentUrl $ReleaseBranch $parentBaseSha

Write-Host "Milestone bump: m$baseMilestone -> m$headMilestone"
Write-Host "Release branch: $ReleaseBranch"
Write-Host "$nativeRepository source: $SkiaBaseBranch @ $nativeBaseSha$(if ($nativeExisting) { ' (release branch already exists)' })"
Write-Host "$parentRepository source: $SkiaSharpBaseBranch @ $parentBaseSha$(if ($parentExisting) { ' (release branch already exists)' })"
Expand All @@ -115,8 +157,9 @@ if (-not $Push) {

# Re-read every source and destination immediately before the first write.
$parentBaseNow = Get-RemoteBranchSha $parentUrl $SkiaSharpBaseBranch
$parentHeadNow = Get-RemoteBranchSha $parentUrl $SkiaSharpHeadBranch
$nativeBaseNow = Get-RemoteBranchSha $nativeUrl $SkiaBaseBranch
if ($parentBaseNow -ne $parentBaseSha -or $nativeBaseNow -ne $nativeBaseSha) {
if ($parentBaseNow -ne $parentBaseSha -or $parentHeadNow -ne $parentHeadSha -or $nativeBaseNow -ne $nativeBaseSha) {
throw 'A source branch changed after preflight. Run the script without -Push again.'
}
$nativeExisting = Assert-Destination $nativeRepository $nativeUrl $ReleaseBranch $nativeBaseSha
Expand Down
Loading