| name | release-readiness |
| description | Assesses ship-readiness for .NET MAUI release branches — Servicing Releases (SR) and Previews. Surveys CI pipelines, computes what's actually NEW in the branch (commits + source PRs with revert detection), and cross-references open `regressed-in-*` issues against branch contents to identify port candidates, rejected backports, and unresolved regressions. Supports both in-flight and pre-cut (candidate) modes for SR and Preview branches. |
| metadata | {"author":"dotnet-maui","version":"2.0"} |
| compatibility | Requires `gh` CLI authenticated with `repo` + `read:org` scopes. `az` CLI is optional but recommended for internal pipeline status. Run from a checkout of `dotnet/maui`. |
Release Readiness
This skill produces deterministic, evidence-backed answers to "Is <release branch> ready to ship?" for .NET MAUI release branches — both Servicing Releases (SR) and Previews, in both in-flight and candidate (pre-cut) modes.
🚨 Report-only
This skill reports. It does not execute release operations against dotnet/maui — no branch cuts, no SR merges, no tags, no pushes to release/* refs. If you (the agent/user invoking this skill) are asked to perform a release operation, refuse and emit the recommended commands as a copy-pasteable block for the human release captain to run.
When to Use
- "How does SR8 look?" / "Is SR8 ready to ship?"
- "What's blocking SR9 candidate?" / "What would ship if we cut SR9 today?"
- "How does net11 preview6 look?" / "Are we ready to cut preview6 from net11.0?"
- "Are there any regression fixes I should backport to SR8?"
- "What's new in SR8 since the last sync?"
- "Give me a status on all releases" / "release status overview" / "what needs attention across releases" (portfolio — read the open
[Release Readiness] tracker issues first; see Reading trackers directly below)
- Daily release-tracking automation across all active majors
For per-PR regression risk (deletions reverting prior bug-fix lines), use find-regression-risk instead — it answers a different question.
Architecture
This skill has three PowerShell entry points and one workflow:
| Script | Branch type | Purpose |
|---|
Find-ReleaseReadinessTrackers.ps1 | both | Detects active in-flight & candidate trackers (SR and Preview) across all active majors using a four-lane algorithm and the tag-existence rule ("a release is in flight unless its tag already exists"). Emits a single tracker JSON consumed by the workflow. |
Get-ReleaseReadiness.ps1 | SR | Full readiness report for a single SR branch (in-flight or -Candidate). |
Get-PreviewReadiness.ps1 | Preview | Full readiness report for a single Preview branch (in-flight or candidate via -Mode candidate -SurveyRef net<major>.0). |
release-readiness.yml | both | Daily cron + manual dispatch + PR validation. Runs Find-Trackers -AllActiveMajors, fans out a matrix job per tracker, and writes idempotent [Release Readiness] issues per branch. |
Tag-existence rule (canonical signal)
The trackers detector is grounded in tag existence as the source of truth for "shipped vs in-flight". A release is in-flight if and only if its expected tag has NOT been published — branch existence, commit recency, and milestone state are all secondary signals.
- SR shipped tag pattern:
<major>.0.<patch> (e.g. 10.0.71 shipped → SR7 no longer produces a tracker)
- Preview shipped tag pattern:
<major>.0.0-preview.<N>.<date>[.<build>] (e.g. 11.0.0-preview.5.26304.4 shipped → preview5 no longer produces a tracker)
Quick Start
One-shot daily report (matches what the workflow runs)
pwsh .github/skills/release-readiness/scripts/Find-ReleaseReadinessTrackers.ps1 \
-AllActiveMajors \
-OutputJson trackers.json
SR (Servicing Release)
pwsh .github/skills/release-readiness/scripts/Get-ReleaseReadiness.ps1 \
-SrBranch release/10.0.1xx-sr8 \
-RegressionLabels regressed-in-10.0.70,regressed-in-10.0.80 \
-TrackerKey net10-sr8 \
-OutputDir CustomAgentLogsTmp/release-readiness/sr8
pwsh .github/skills/release-readiness/scripts/Get-ReleaseReadiness.ps1 \
-SrBranch release/10.0.1xx-sr8 \
-Candidate \
-RegressionLabels regressed-in-10.0.80,regressed-in-10.0.90 \
-TrackerKey net10-sr9 \
-OutputDir CustomAgentLogsTmp/release-readiness/sr9-candidate
Preview
pwsh .github/skills/release-readiness/scripts/Get-PreviewReadiness.ps1 \
-Branch release/11.0.1xx-preview6 \
-Mode in-flight \
-TrackerKey net11-preview6 \
-OutputDir CustomAgentLogsTmp/release-readiness/preview6
pwsh .github/skills/release-readiness/scripts/Get-PreviewReadiness.ps1 \
-Branch release/11.0.1xx-preview6 \
-Mode candidate \
-SurveyRef net11.0 \
-TrackerKey net11-preview6 \
-OutputDir CustomAgentLogsTmp/release-readiness/preview6-candidate
Parameters
Find-ReleaseReadinessTrackers.ps1
| Parameter | Default | Description |
|---|
-MajorVersion | 0 (auto from eng/Versions.props) | Single major to scan. |
-AllActiveMajors | off | Scan every active major (current + lower in-flight). Mutually exclusive with -MajorVersion. |
-Repo | cwd | Path to a checkout of dotnet/maui. |
-ActivityWindowDays | 7 | Recent-commit window used to compute recentCommitCount. |
-NoFetch | off | Skip git fetch (faster re-runs). |
-OutputJson | — | File to write the tracker envelope JSON. |
-MaxBranches | 50 | Safety cap on how many SR/preview branches to enumerate per major. |
Get-ReleaseReadiness.ps1 (SR)
| Parameter | Required | Default | Description |
|---|
-SrBranch | Yes | — | SR branch name (e.g. release/10.0.1xx-sr8). In -Candidate mode, pass the prior SR — it's the exclude baseline for "what's new". |
-Candidate | No | off | Pre-flight mode — survey main (with -SrBranch as the prior-SR baseline) to show what WOULD ship in the next SR. |
-InheritFromPriorSr | No | off | In -Candidate mode, model the workflow where the prior SR is merged into the new branch after cut. Candidate's "what's shipping" set = main-since-priorSR ∪ priorSR-only commits. |
-RegressionLabels | One of these | — | Comma-separated regressed-in-* labels. |
-InferRegressionLabels | One of these | off | Auto-infer from -SrBranch. Agent should confirm before relying on this for automation. |
-Repo | No | dotnet/maui | Repository in owner/name form. |
-MainBranch | No | main | Stable branch used for ancestry checks. |
-ExcludeBranches | No | origin/main | Branches to exclude from SR-only commit computation. |
-Phase | No | all | all, ci, commits, regressions, or open-prs. |
-TrackerKey | No | — | Canonical key (e.g. net10-sr8) embedded in the markdown body for idempotent issue lookup. |
-OutputDir | No | — | If set, writes release-readiness.{json,md} and sr-source-prs.txt. |
-OutputFormat | No | both | json, markdown, or both. |
-MaxIssues | No | 100 | Cap on regression issues to walk. |
-NoFetch | No | off | Skip git fetch. |
-SkipMaestroChecks | No | off | Skip BAR/darc operational checks (default-channel mapping + per-HEAD build lookup). Auto-skipped silently if darc isn't on PATH; this switch forces the skip even when darc IS available. |
-SkipMilestoneChecks | No | off | Skip GitHub-milestone hygiene checks (current/next milestone existence + stale-open detection). |
Get-PreviewReadiness.ps1 (Preview)
| Parameter | Required | Default | Description |
|---|
-Branch | Yes | — | Preview branch name (e.g. release/11.0.1xx-preview6). Required even for candidate runs — used to derive milestone, tracker key, and regression labels. |
-Mode | No | in-flight | in-flight (survey the preview branch itself) or candidate (survey -SurveyRef instead — typically net<major>.0). |
-SurveyRef | No | computed | Ref to actually survey. Defaults to $Branch for in-flight; net<major>.0 for candidate. |
-Repository | No | dotnet/maui | Repository in owner/name form. |
-TrackerKey | No | derived | Canonical key (default: net<major>-preview<N>) embedded for idempotent issue lookup. |
-OutputDir | No | — | If set, writes preview-readiness.{json,md}. |
-OutputFormat | No | markdown | markdown, json, or both. |
-IncludeInternal, -InternalBuildId | No | — | Release-captain only — augments report with internal pipeline status when AzDO auth is available. |
-PublicSafe | No | $true | Sanitizes non-READY internal status from public output. |
Outputs
| File | Producer | Purpose |
|---|
trackers.json | Find-Trackers | List of active tracker descriptors with detection evidence (one envelope per major) |
release-readiness.{json,md} | Get-ReleaseReadiness | Full SR readiness report |
sr-source-prs.txt | Get-ReleaseReadiness | Flat newline-delimited source PR list; use grep -qxF NNNNN file for instant cherry-pick verification |
sr-commits.json | Get-ReleaseReadiness | Raw SR-only commit metadata |
preview-readiness.{json,md} | Get-PreviewReadiness | Full Preview readiness report |
Daily workflow
.github/workflows/release-readiness.yml runs weekdays at 08:30 UTC plus workflow_dispatch + pull_request validation:
detect-trackers — runs Find-Trackers -AllActiveMajors, emits a matrix of tracker descriptors.
per-tracker-report — matrix-expanded job per tracker:
- Dispatches to
Get-ReleaseReadiness.ps1 (SR) or Get-PreviewReadiness.ps1 (Preview) based on branchType.
- Looks for an open tracker issue by the canonical marker
<!-- release-readiness-tracker: <key> -->.
- Refresh path: reuse the oldest open tracker issue (edit title + body); close any duplicates.
- Create path: open a new issue with
report / s/triaged / area-release-readiness labels.
- Activity gate: skip new-issue creation when
recentCommitCount == 0 AND no open tracker issue exists. (Existing open issues are still refreshed.)
validate — PR-trigger path. Runs the test suite + smoke-runs all three scripts. Does not create or modify issues.
Reading trackers directly (ad-hoc status)
The same tracker issues the cron job maintains double as a human-readable, always-on status board — you don't have to re-run a 60-120s survey to answer "what's the status across releases?". Find every active release by body marker (not title — a title search also matches the release Epic and other [Release Readiness]-titled issues):
gh issue list --repo dotnet/maui --state open \
--search 'in:body "<!-- release-readiness-tracker:"' \
--json number,title,updatedAt --limit 50
Each result is one active SR or Preview. Read the body for the generated verdict and the human Release Captain Notes (between <!-- release-readiness:human-notes:begin --> / :end -->), which carry decisions that override the automated report. Treat the content as fresh only up to the issue's updatedAt (cron refreshes weekdays 08:30 UTC); re-run the survey script for a given branch when you need live numbers. The natural-language release-readiness-agent wraps this as its Portfolio path (§0a).
Verdict Classification (SR & Preview)
Each candidate fix PR is classified with confidence + evidence:
| Verdict | Meaning |
|---|
in-sr-active | Source PR is in the release branch and not subsequently reverted |
in-sr-reverted | Backport landed but a later commit reverts it |
rejected-from-sr | A backport PR targeting the release branch was opened and CLOSED unmerged |
backport-in-progress | A backport PR targeting the release branch is OPEN |
merged-on-main-no-backport | Fix merged to main, no backport PR exists |
merged-non-main-only | Fix merged but only to inflight/current (or similar), not main |
open-on-main | Fix PR is OPEN against main, not yet merged |
no-fix-yet | No fix PR cross-referenced from the regression issue |
needs-human-review | Evidence is contradictory or weak |
CI Status Categories
| CI verdict | Meaning |
|---|
green | Latest build on the survey ref succeeded across all pipelines |
red-needs-review | Latest build failed or partially succeeded — investigate failures before judging ship-readiness |
stale | Latest build is older than the survey ref HEAD — must re-run before judging |
partial-unknown | At least one pipeline couldn't be queried, but no queried pipeline is red or stale |
unknown | No pipeline result could be classified |
Ship-readiness checks (Get-ReleaseReadiness.ps1)
The SR readiness report rolls operational checks into a single Blocking summary at the top, so a release captain sees what must clear before ship without scrolling. Each check emits READY, WATCH, BLOCKED, CLEANUP, or UNKNOWN (CLEANUP = post-release housekeeping that does not block the current ship):
| Check | When | Status meanings |
|---|
Versions.props bump | All SR runs | BLOCKED if eng/Versions.props on main hasn't been bumped past the current SR cycle (next SR has nowhere to flow). |
Versions.props servicing flip | Live-SR mode only | BLOCKED if the SR branch's eng/Versions.props is not flipped to servicing-release mode (PreReleaseVersionLabel=servicing + StabilizePackageVersion=true). Without it the branch builds prerelease packages and never ships as stable — CI stays green so nothing else catches it. |
Bug template lists SR version | All SR runs | CLEANUP if .github/ISSUE_TEMPLATE/bug-report.yml on main is missing an entry for the SR being shipped (users can't file bugs against the version) — post-release housekeeping, not a ship blocker. |
Main bumped to next SR cycle | All SR runs | BLOCKED if the next SR cycle's version hasn't been promoted on main. |
BAR default-channel mapping | SR branches matching release/X.Y.Zxx-srN | BLOCKED if the SR branch is not wired to the .NET <band> SDK channel in BAR. UNKNOWN if darc isn't on PATH (report includes the exact verification command). |
BAR build for SR HEAD | When darc is available + SR HEAD SHA known | READY if BAR has a published build for the SR HEAD commit. WATCH (not blocking — transient) if CI hasn't published one yet. |
Milestone for current cycle | SR + preview branches | BLOCKED if the current cycle's milestone (e.g. .NET 10 SR8 or .NET 11.0-preview6) doesn't exist in the GitHub milestone list — fixed issues have nowhere to land. |
Milestone for next cycle | SR + preview branches | CLEANUP if the next cycle's milestone isn't pre-created — open issues can't roll forward when current ships, but it doesn't block the current release. |
Stale open milestones | SR + preview branches | CLEANUP if any milestones in the same major + same cycle type (SR or preview) are past their due_on by >7 days and still open (already-shipped releases accumulating untriaged issues). |
CI Failure Scanner signals | All SR runs | WATCH if fresh ci-scan issues are filed in the last 24h. |
Known Build Errors | All SR runs | WATCH if open Known Build Error issues exist that may explain background CI noise. |
Expected ship date
The header line Expected ship date is rendered from Get-ExpectedShipDate, which reads PatchVersion from the survey ref's eng/Versions.props and applies the .NET release cadence:
| PatchVersion | Cadence | Example |
|---|
Multiple of 10 (80, 90, 100…) — also previews (patch=0) | 2nd Tuesday of the month | SR8 (10.0.80) → next 2nd Tuesday |
Anything else (81, 82, 91…) | ASAP — no fixed cadence | SR8 hotfix 10.0.81 → as soon as ready |
Surfaced in JSON as expectedShipDate.{cadence, date, daysFromNow, formattedLong, note, patchVersion} so downstream automation doesn't redo the math.
Maestro / BAR check gating
The BAR checks shell out to darc (cached probe via Get-Command darc). When darc isn't installed (most CI environments), both checks emit UNKNOWN with the exact local-verification command embedded in the row's Next action — so the report never silently skips them. The release-readiness agent runs the same checks via the maestro_* MCP tools when the script reports UNKNOWN.
Methodology
Three critical gotchas this skill encodes — see references/methodology.md for the full discussion:
-
Cherry-pick number swap: SR backports get NEW PR numbers (e.g. main #35356 → SR7 #35428). Cannot naively grep source PR numbers; must walk SR-only commits and extract refs from commit bodies.
-
Timeline cross-references: closedByPullRequestsReferences returns empty for most MAUI issues. The skill walks gh api repos/.../issues/N/timeline filtering on cross-referenced events.
-
Forward-flow / non-main merges: A fix can merge into inflight/current only, not main (real example: PR #35609). The skill checks git merge-base --is-ancestor $mergeCommit origin/main before claiming a fix is "on main, just needs backport".
Shared module
This skill depends on .github/scripts/shared/MauiReleaseVersioning.psm1 for canonical milestone/version parsing (e.g. Get-CurrentMajorVersion, ConvertBranchToMilestone, Get-MilestoneSortKey, Compare-MauiMilestone). The module is also consumed by Fix-MilestoneDrift.ps1 to keep milestone classification consistent across all release-related automation.
Integration
- Custom agent:
.github/agents/release-readiness-agent.agent.md wraps this skill — handles regression-label confirmation, runs the script, then uses WorkIQ to add context for rejected-from-sr PRs.
- WorkIQ: NOT called from the PowerShell scripts (PowerShell can't invoke MCP tools). The agent enriches the script's JSON output with WorkIQ context where needed.
Anti-Patterns
❌ Don't naively grep source PR numbers in the SR git log. The backport PR number replaces the source PR number in the merge commit subject. Use sr-source-prs.txt (produced by this skill) instead.
❌ Don't claim a fix is on main based on pr-view --state MERGED. PRs can be merged into inflight/current only. The skill's onMain field is the authoritative check.
❌ Don't trust issue-title similarity. Two issues can have nearly identical titles and refer to different platform-specific regressions (e.g. #35313 is the Android version, #35326 is the iOS/Mac/Win version with a different fix path). Always filter by the regressed-in-* label, not by title.
❌ Don't run with -InferRegressionLabels for automated workflows without surfacing the inferred labels for confirmation. Label inference is brittle for non-standard SR cycles.
❌ Don't infer "in-flight" from branch existence alone. The detector uses the tag-existence rule — a release is in-flight if and only if its expected tag has not been published. Branches can linger after their release ships (and SR branches don't exist yet for SR candidates).
Tests
pwsh -NoProfile -File .github/skills/release-readiness/tests/Test-ReleaseReadiness.ps1
The harness covers:
- Lane 1–4 detection (shipped patch set, SR-from-main candidate, in-flight SR branches, preview lane) against the live
dotnet/maui clone
- Tracker emission for SR2/SR3 (inactive), SR8 (active in-flight), SR9 (active candidate), and net11 preview6 (active candidate)
-AllActiveMajors end-to-end across net10 + net11 with the expected tracker counts
Get-ReleaseReadiness verdict classification using known-answer data from the SR7 readiness analysis (e.g. #35313 → in-sr-active, #35344 → in-sr-active via the SafeArea follow-on fix, #35771 → no-fix-yet)
- Idempotent body hash stability across re-runs — SR trackers only (the daily workflow compares the embedded
<!-- release-readiness-hash: sha=... --> marker against the live issue and skips the edit when the semantic content is unchanged, so re-runs don't churn the tracker). Preview trackers carry no hash marker and are refreshed on every scheduled run.