diff --git a/.github/workflows/release.md b/.github/release-process.md similarity index 51% rename from .github/workflows/release.md rename to .github/release-process.md index 87005a64a..41f772759 100644 --- a/.github/workflows/release.md +++ b/.github/release-process.md @@ -10,7 +10,7 @@ The following process is used when publishing new releases to NuGet.org. ## 2. Prepare the release -From a local clone of the repository, use Copilot CLI to invoke the `prepare-release` skill. The skill assesses the semantic version, bumps the version in [`src/Directory.Build.props`](../../src/Directory.Build.props), runs API compatibility checks, reviews documentation, drafts release notes, and creates a pull request with all release artifacts. +From a local clone of the repository, use Copilot CLI to invoke the `prepare-release` skill. The skill assesses the semantic version, bumps the version in [`src/Directory.Build.props`](../src/Directory.Build.props), runs API compatibility checks, reviews documentation, drafts release notes, and creates a pull request with all release artifacts. Review the PR, request changes if needed, and merge when ready. @@ -20,6 +20,15 @@ After the prepare-release PR is merged, invoke the `publish-release` skill. The Review the draft release on GitHub, check 'Set as a pre-release' if appropriate, and click 'Publish release'. +## Branching + +The `main` branch is the next-MAJOR preview and development line; currently, it produces the `2.0.0-preview.*` series. Nightly `cron` CI on `main` publishes CI-suffixed packages to GitHub Packages. +Long-lived `release/{MAJOR}.x` branches are created on demand when a shipped MAJOR needs servicing releases. Every push to a `release/*` branch publishes a CI-suffixed package to GitHub Packages, so servicing CI packages are commit-driven rather than clock-driven. +Short-lived `release-{version}` branches are local prepare-release work branches that become pull requests, such as `release-2.0.0-preview.1` or `release-1.3.1`. +Official NuGet.org publishes occur only when a GitHub Release is created from a branch's tag. +The prepare-release skill asks for the source/base branch first so the release PR targets the same line it assessed. +For the agent-facing, structured version of these rules, see [release-branches.md](skills/shared-resources/release-branches.md). + ## 4. Monitor the Release workflow - After publishing, a workflow will produce build artifacts and publish the NuGet packages to NuGet.org diff --git a/.github/skills/bump-version/SKILL.md b/.github/skills/bump-version/SKILL.md index 29932b4db..e78915636 100644 --- a/.github/skills/bump-version/SKILL.md +++ b/.github/skills/bump-version/SKILL.md @@ -8,18 +8,21 @@ compatibility: Requires gh CLI with repo access for creating branches and pull r Assess and bump the SDK version in `src/Directory.Build.props` to prepare for the next release. This skill owns the [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) assessment logic — the [SemVer assessment guide](references/semver-assessment.md) is the single source of truth for version assessment criteria used across the release workflow by both the **prepare-release** and **publish-release** skills. +Use the shared [release branch reference](../shared-resources/release-branches.md) for branch roles, previous-release lookup rules, and release work-branch naming. + > **Note**: For comprehensive release preparation — including ApiCompat/ApiDiff, documentation review, and release notes — use the **prepare-release** skill, which incorporates version assessment as part of its broader workflow. ## Process ### Step 1: Read Current Version and Previous Release -Read `src/Directory.Build.props` on the default branch and extract: +Read `src/Directory.Build.props` on the current branch and extract: - `` — the `MAJOR.MINOR.PATCH` version +- `` — the prerelease suffix, when present -Display the current version to the user. +The candidate version is `{VersionPrefix}` plus `-{VersionSuffix}` when the suffix is present (for example, `2.0.0-preview.1`). Display the current candidate version to the user. -Determine the previous release tag from `gh release list` (most recent **published** release). Draft releases must be ignored — they represent a pending release that has not yet shipped. Use `--exclude-drafts` or filter to only published releases when querying. +Determine the previous release tag from `gh release list` (most recent **published** release). Draft releases must be ignored — they represent a pending release that has not yet shipped. Use `--exclude-drafts` or filter to only published releases when querying. The lookup is branch-aware: from a `release/{MAJOR}.x` branch, restrict candidates to tags matching `v{MAJOR}.*`; from `main`, use the most recent published release globally. See [release-branches.md](../shared-resources/release-branches.md) for details. ### Step 2: Assess and Determine Next Version @@ -40,26 +43,31 @@ When context about queued changes is available or can be gathered, assess the ve #### Default Suggestion (Fallback) -When a quick bump is needed without full change analysis, suggest the next **minor** version: +When a quick bump is needed without full change analysis, suggest based on the candidate version: -- Current `1.0.0` → suggest `1.1.0` -- Current `1.2.3` → suggest `1.3.0` +- **Stable candidate** — suggest the next **minor** version: + - Current `1.0.0` → suggest `1.1.0` + - Current `1.2.3` → suggest `1.3.0` +- **Prerelease candidate** — if the suffix starts with an identifier such as `preview.` or `rc.` followed by an integer, suggest incrementing the trailing integer: + - Current `2.0.0-preview.1` → suggest `2.0.0-preview.2` + - Current `2.0.0-rc.1` → suggest `2.0.0-rc.2` Present the suggestion and let the user confirm or provide an alternative. -Parse the confirmed version into its `VersionPrefix` component. +Parse the confirmed version into its `VersionPrefix` and `VersionSuffix` components. Stable versions have no suffix. ### Step 3: Create Pull Request -1. Create a new branch named `bump-version-to-{version}` (e.g. `bump-version-to-1.1.0`) from the default branch +1. Create a new branch named `bump-version-to-{version}` (e.g. `bump-version-to-1.1.0`) from the current branch 2. Update `src/Directory.Build.props`: - - Set `` to the new version + - Set `` to the confirmed stable component + - Set `` for prerelease versions, or clear it for stable versions; add the element if it is missing - Update `` if the MAJOR version has changed 3. Commit with message: `Bump version to {version}` 4. Push the branch and create a pull request: - **Title**: `Bump version to {version}` - **Label**: `infrastructure` - - **Base**: default branch + - **Base**: the current branch (which, for a servicing branch like `release/1.x`, is that servicing branch — not `main`) ### Step 4: Confirm diff --git a/.github/skills/bump-version/references/semver-assessment.md b/.github/skills/bump-version/references/semver-assessment.md index 2d39ec166..f1f5f6d68 100644 --- a/.github/skills/bump-version/references/semver-assessment.md +++ b/.github/skills/bump-version/references/semver-assessment.md @@ -56,13 +56,31 @@ Recommend a PATCH version increment if no MAJOR or MINOR criteria are met. - MINOR: `MAJOR.(MINOR+1).0` - PATCH: `MAJOR.MINOR.(PATCH+1)` -**Examples** from previous release `v1.2.0`: +### Prereleases -| Level | Recommended | -|-------|-------------| -| PATCH | `v1.2.1` | -| MINOR | `v1.3.0` | -| MAJOR | `v2.0.0` | +While the candidate version uses a prerelease suffix (e.g., `X.Y.Z-preview.N`, `X.Y.Z-rc.N`), the recommended next version increments the trailing integer of the suffix: `preview.3` → `preview.4`, `rc.1` → `rc.2`. + +Going to GA drops the suffix entirely: `2.0.0-rc.2` → `2.0.0`. + +This is purely about how to *compute* the next version. It does **not** declare any new policy about what kinds of changes are permitted between previews — refer to the existing [versioning documentation](../../../../docs/versioning.html) for breaking-change policy. + +### Branch context + +The "previous release" lookup is constrained to tags matching `v{MAJOR}.*` when assessing from a `release/{MAJOR}.x` servicing branch. On `main`, the lookup is unconstrained (most recent published release globally). + +The MAJOR/MINOR/PATCH classification criteria above are unchanged regardless of branch. + +See [release-branches.md](../../shared-resources/release-branches.md) for branch-role definitions and previous-release lookup rules. + +**Examples**: + +| Previous release | Branch | Level | Recommended | +|--------------------|---------------|-----------------------|------------------------| +| `v1.2.0` | `main` | PATCH | `v1.2.1` | +| `v1.2.0` | `main` | MINOR | `v1.3.0` | +| `v1.2.0` | `main` | MAJOR | `v2.0.0` | +| `v2.0.0-preview.1` | `main` | (prerelease bump) | `v2.0.0-preview.2` | +| `v1.3.0` | `release/1.x` | PATCH | `v1.3.1` | ## Comparing Against the Candidate Version @@ -83,6 +101,7 @@ Present the assessment as a summary table followed by a rationale: | Aspect | Finding | |--------|---------| +| Branch context | release/1.x | | Previous release | v1.0.0 | | Breaking changes | None confirmed | | New API surface | Yes — 3 PRs add new public APIs | diff --git a/.github/skills/prepare-release/SKILL.md b/.github/skills/prepare-release/SKILL.md index 03976ac91..f881332a1 100644 --- a/.github/skills/prepare-release/SKILL.md +++ b/.github/skills/prepare-release/SKILL.md @@ -12,22 +12,39 @@ Prepare a new release for the `modelcontextprotocol/csharp-sdk` repository. This > **User confirmation required: This skill NEVER pushes a branch or creates a pull request without explicit user confirmation.** The user must review and approve all details before any remote operations occur. +Use the shared [release branch reference](../shared-resources/release-branches.md) for branch roles, previous-release lookup rules, and release work-branch naming. + ## Process Work through each step sequentially. Present findings at each step and get user confirmation before proceeding. Skip any step that has no applicable items. -### Step 1: Determine Target and Gather PRs +### Step 1: Select Source Branch + +List candidate source/base branches via: +`gh api repos/{owner}/{repo}/branches --paginate --jq '[.[] | select(.name == "main" or (.name | startswith("release/"))) | .name]'` + +Present the list to the user and ask them to choose the source/base branch. Default selection: `main`. + +The selected branch drives every subsequent step: +1. The branch on which the candidate version is read from `src/Directory.Build.props`. +2. The "previous release" lookup (constrained to `v{MAJOR}.*` on `release/{MAJOR}.x`). +3. The commit range from which PRs are collected. +4. The PR base (`--base`) for `gh pr create` at the end of the skill. + +See [release-branches.md](../shared-resources/release-branches.md) for the structured branch rules. + +### Step 2: Determine Target and Gather PRs The user may provide: -- **A git ref** (commit SHA, branch, or tag) — use as the target commit -- **No context** — show the last 5 commits on `main` (noting HEAD) and offer the option to enter a branch or tag name instead +- **A git ref** (commit SHA, branch, or tag) — use as the target commit relative to the selected source/base branch +- **No context** — show the last 5 commits on the selected source/base branch (noting HEAD) and offer the option to enter a branch or tag name instead Once the target is established: -1. Determine the previous release tag from `gh release list` (most recent **published** release — exclude drafts with `--exclude-drafts`). -2. Get the full list of PRs merged between the previous release tag and the target commit. -3. Read `src/Directory.Build.props` **at the target commit**. Extract `` as the **candidate version**. +1. Determine the previous release tag from `gh release list` (most recent **published** release — exclude drafts with `--exclude-drafts`). Use the selected source/base branch context: on `release/{MAJOR}.x`, restrict candidates to tags matching `v{MAJOR}.*`; on `main`, use the most recent published release globally. +2. Get the full list of PRs merged between the previous release tag and the target commit on the selected branch. +3. Read `src/Directory.Build.props` **at the target commit**. Extract `` and ``; the **candidate version** is `{VersionPrefix}` plus `-{VersionSuffix}` when the suffix is present (for example, `2.0.0-preview.1`). -### Step 2: Categorize and Attribute +### Step 3: Categorize and Attribute Sort every PR into one of four categories. See [references/categorization.md](references/categorization.md) for detailed guidance. @@ -50,53 +67,55 @@ Sort every PR into one of four categories. See [references/categorization.md](re - Omit the co-author parenthetical when there are none - Sort entries within each section by merge date (chronological) -### Step 3: Breaking Change Audit +### Step 4: Breaking Change Audit Invoke the **breaking-changes** skill with the commit range from the previous release tag to the target commit. Examine every PR, assess impact, reconcile labels (offering to add/remove labels and comment on PRs), and get user confirmation. Use the results (confirmed breaking changes with impact ordering and detail bullets) in the remaining steps. -### Step 4: Assess Release Version +### Step 5: Assess Release Version -Using the categorized PRs from Step 2 and confirmed breaking changes from Step 3, assess the appropriate [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) release level. Follow the [SemVer assessment guide](../bump-version/references/semver-assessment.md) (owned by the **bump-version** skill) for the full assessment criteria. +Using the categorized PRs from Step 3 and confirmed breaking changes from Step 4, assess the appropriate [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) release level. Follow the [SemVer assessment guide](../bump-version/references/semver-assessment.md) (owned by the **bump-version** skill) for the full assessment criteria. 1. **Classify the release level**: - **MAJOR** — if any confirmed breaking changes are present (API or behavioral), excluding changes to `[Experimental]` APIs - **MINOR** — if no breaking changes but new public APIs, features, or obsoletion warnings are introduced - **PATCH** — otherwise -2. **Compute the recommended version** from the previous release tag: +2. **Compute the recommended version** from the previous release tag and branch context: - Increment the appropriate component (MAJOR resets MINOR.PATCH to 0; MINOR resets PATCH to 0) + - For prerelease candidates such as `preview.N` or `rc.N`, the recommendation may simply increment the trailing integer per the assessment guide 3. **Compare against the candidate version** from `src/Directory.Build.props`. Flag any discrepancy: - **Under-versioned**: The candidate is lower than the recommended level. This is a concern that should be resolved. - **Over-versioned**: The candidate is higher than strictly required. This is acceptable under SemVer but worth noting. 4. **Present the assessment** with a summary table showing the previous release, change classification, recommended level, recommended version, and any discrepancy with the candidate. Include a brief rationale citing the most significant PRs. 5. **Get user confirmation** of the release version before proceeding. -### Step 5: Create Release Branch and Bump Version +### Step 6: Create Release Branch and Bump Version After the version is confirmed: -1. Create a local branch named `release-{version}` from the target commit (e.g., `release-1.1.0`). +1. Create a local branch named `release-{version}` from the target commit (e.g., `release-2.0.0-preview.1`, `release-1.3.1`). 2. Update `src/Directory.Build.props`: - - Set `` to the confirmed version - - Update `` if the MAJOR version has changed (set to the previous release version) + - Set `` to the confirmed stable component + - Set `` for prerelease versions, or clear it for stable versions; add the element if it is missing + - Update `` when appropriate. For the `2.0.0-preview` series, baseline is `1.3.0` (latest shipped 1.x). For subsequent stable releases, baseline is the previous shipped version of the same MAJOR or the latest stable from the previous MAJOR. 3. Build the solution to verify the version change compiles: `dotnet build` This step creates local changes only — nothing is committed or pushed yet. -### Step 6: Run API Compatibility Check +### Step 7: Run API Compatibility Check Run API compatibility validation against the baseline version. Follow [references/apicompat-apidiff.md](references/apicompat-apidiff.md) for the full procedure. 1. Run `dotnet pack` to trigger package validation against `PackageValidationBaselineVersion` 2. Capture the ApiCompat output (compatibility issues, warnings, suppressions) 3. If there are unexpected compatibility breaks: - - Cross-reference with the breaking change audit from Step 3 + - Cross-reference with the breaking change audit from Step 4 - Present any unaccounted breaks to the user - If breaks are intentional, add appropriate entries to `CompatibilitySuppressions.xml` in the affected project directory 4. Record the ApiCompat results for inclusion in the PR description -### Step 7: Generate API Diff Report +### Step 8: Generate API Diff Report Generate a human-readable diff of the public API surface between the previous release and the new version. Follow [references/apicompat-apidiff.md](references/apicompat-apidiff.md) for the full procedure, including how to install the `Microsoft.DotNet.ApiDiff.Tool` from the .NET transport feed. @@ -107,7 +126,7 @@ Generate a human-readable diff of the public API surface between the previous re > **If the ApiDiff tool cannot be installed or fails to produce output, STOP and inform the user.** Present the error and ask how to proceed. Do not fall back to a manual summary — the user must decide whether to troubleshoot, skip the API diff, or abort. -### Step 8: Review and Update Documentation +### Step 9: Review and Update Documentation Review repository documentation for changes needed to compensate for or adapt to this release: @@ -118,12 +137,12 @@ Review repository documentation for changes needed to compensate for or adapt to Stage all documentation changes for inclusion in the release commit. -### Step 9: Draft Release Notes +### Step 10: Draft Release Notes Compose the release notes that will appear in the PR description and serve as the foundation for the **publish-release** skill. This is a draft — the final release notes will be refreshed when the GitHub release is created. 1. **Preamble** — Draft a short paragraph summarizing the release theme. Present it to the user for review and editing. The preamble is **required**. -2. **Breaking Changes** — sorted most → least impactful (from Step 3 results). Include the versioning docs link. +2. **Breaking Changes** — sorted most → least impactful (from Step 4 results). Include the versioning docs link. 3. **What's Changed** — chronological; includes breaking change PRs 4. **Documentation Updates** — chronological 5. **Test Improvements** — chronological @@ -132,11 +151,11 @@ Compose the release notes that will appear in the PR description and serve as th - New contributors (first contribution in this release) - Issue reporters (cite resolving PRs) - PR reviewers (single bullet, sorted by review count, no count shown) -8. **Full Changelog** link +8. **Full Changelog** link using the exact tag, including any suffix (for example, `v1.3.1` or `v2.0.0-preview.1`) -Omit empty sections. Present each section for user review before proceeding. +Omit empty sections. Present each section for user review before proceeding. Tag references in templates use `v{version}` exactly, including prerelease suffixes; the Full Changelog link compares the previous tag to the suffixed tag when applicable. -### Step 10: Commit Changes +### Step 11: Commit Changes Commit all changes to the `release-{version}` branch: @@ -144,39 +163,40 @@ Commit all changes to the `release-{version}` branch: 2. Commit with message: `Prepare release v{version}` 3. Do **not** push yet -### Step 11: Present Release Summary +### Step 12: Present Release Summary -Present **all** of the following details to the user for review. The user must confirm every aspect before proceeding to Step 12. +Present **all** of the following details to the user for review. The user must confirm every aspect before proceeding to Step 13. 1. **Version number** with brief rationale for why this SemVer level was selected -2. **Branch name** (e.g., `release-1.1.0`) -3. **Remote** the branch would be pushed to (show the configured remote, typically `origin`) -4. **Files changed** — list every file modified in the commit with a one-line summary of what changed in each: +2. **Source/base branch** selected in Step 1 +3. **Branch name** (e.g., `release-2.0.0-preview.1`, `release-1.3.1`) +4. **Remote** the branch would be pushed to (show the configured remote, typically `origin`) +5. **Files changed** — list every file modified in the commit with a one-line summary of what changed in each: ``` - src/Directory.Build.props — Version bumped from 1.0.0 to 1.1.0 + src/Directory.Build.props — Version bumped from 2.0.0-preview.1 to 2.0.0-preview.2 src/ModelContextProtocol.Core/CompatibilitySuppressions.xml — Added 2 new suppressions README.md — Updated code sample for new API docs/experimental.md — Added new experimental API reference ``` -5. **Draft release notes** — the complete release notes from Step 9 -6. **API Compatibility results** — the ApiCompat output from Step 6 -7. **API Diff report** — the API diff from Step 7 -8. **Proposed PR title** (e.g., `Release v1.1.0`) -9. **Proposed PR description** — the assembled content combining release notes, ApiCompat, and ApiDiff +6. **Draft release notes** — the complete release notes from Step 10 +7. **API Compatibility results** — the ApiCompat output from Step 7 +8. **API Diff report** — the API diff from Step 8 +9. **Proposed PR title** (e.g., `Release v2.0.0-preview.1`, `Release v1.3.1`) +10. **Proposed PR description** — the assembled content combining release notes, ApiCompat, and ApiDiff After presenting all details, explicitly ask the user: > Would you like to push the branch and create the pull request? **Do not proceed without explicit "yes" confirmation.** -### Step 12: Push Branch and Create Pull Request +### Step 13: Push Branch and Create Pull Request -Only after explicit user confirmation in Step 11: +Only after explicit user confirmation in Step 12: 1. Push the `release-{version}` branch to the remote -2. Create a pull request: +2. Create a pull request with `gh pr create --base {step-1-branch}`: - **Title**: `Release v{version}` - - **Base**: default branch (typically `main`) + - **Base**: the source/base branch selected in Step 1 - **Head**: `release-{version}` - **Description**: The assembled PR description (see PR Description Template below) - **Labels**: Apply appropriate labels (e.g., `release`) @@ -192,18 +212,20 @@ Only after explicit user confirmation in Step 11: - **Single breaking change**: use the same numbered format as multiple - **No user-facing changes**: if all PRs are documentation, tests, or infrastructure, flag that a release may not be warranted and ask the user whether to proceed - **Version discrepancy**: if the candidate version from `Directory.Build.props` doesn't match the SemVer assessment, present the discrepancy and let the user decide the final version +- **Proposed MAJOR does not match branch MAJOR**: if the proposed version's MAJOR doesn't match the branch's MAJOR (for example, proposing `2.0.0-preview.2` on `release/1.x`), flag this as a warning and ask the user to confirm. Do not hard-fail. This is informational, not a policy enforcement. +- **Prerelease bump**: when the candidate version has a suffix like `preview.N`, the SemVer assessment may simply increment `N` rather than computing MAJOR/MINOR/PATCH. Refer to the SemVer assessment guide's Prereleases section. - **No previous release**: if this is the first release, there is no previous tag; gather all PRs merged to the target - **ApiCompat tooling unavailable**: fall back to `dotnet pack` output; note in the PR description that full ApiCompat was run via package validation only - **API diff tool installation fails**: do not fall back to a manual summary; pause and present the installation error to the user, offering options to troubleshoot, skip the API diff section, or abort the release preparation - **No changelogs in repo**: skip changelog updates; note in the summary - **Branch already exists**: if `release-{version}` already exists locally or remotely, ask the user whether to reuse it, delete and recreate, or choose a different name -- **PackageValidationBaselineVersion update**: when bumping MAJOR version, update the baseline to the previous release version; when bumping MINOR or PATCH, keep the existing baseline +- **PackageValidationBaselineVersion update**: for the `2.0.0-preview` series, use `1.3.0`; for subsequent stable releases, use the previous shipped version of the same MAJOR or the latest stable from the previous MAJOR - **CompatibilitySuppressions.xml**: when intentional breaks are found, add suppression entries and include the file in the commit; existing suppressions should be preserved -- **User declines PR creation**: if the user declines at Step 11, leave the local branch intact so they can review, modify, or push manually +- **User declines PR creation**: if the user declines at Step 12, leave the local branch intact so they can review, modify, or push manually ## PR Description Template -The PR description combines release notes, ApiCompat, and ApiDiff into a single document. Omit empty sections. +The PR description combines release notes, ApiCompat, and ApiDiff into a single document. Omit empty sections. The `{version}` placeholder is the full version and may include a prerelease suffix (for example, `Release v2.0.0-preview.1`). ```markdown # Release v{version} @@ -241,7 +263,8 @@ Refer to the [C# SDK Versioning](https://csharp.sdk.modelcontextprotocol.io/vers * @user made their first contribution in #PR * @user1 @user2 @user3 reviewed pull requests -**Full Changelog**: https://github.com/modelcontextprotocol/csharp-sdk/compare/previous-tag...release-{version} +**Full Changelog**: https://github.com/modelcontextprotocol/csharp-sdk/compare/{previous-tag}...v{version} + --- @@ -266,7 +289,7 @@ Refer to the [C# SDK Versioning](https://csharp.sdk.modelcontextprotocol.io/vers ## Release Notes Template -The release notes section within the PR description uses the same format as the final GitHub release notes (used by the **publish-release** skill). This ensures consistency between the PR and the published release. +The release notes section within the PR description uses the same format as the final GitHub release notes (used by the **publish-release** skill). This ensures consistency between the PR and the published release. Tag examples such as `v2.0.0-preview.1` are valid and should be used verbatim when the version has a prerelease suffix. Omit empty sections. The preamble is **always required** — it is not inside a section heading. @@ -303,5 +326,6 @@ Refer to the [C# SDK Versioning](https://csharp.sdk.modelcontextprotocol.io/vers * @user submitted issue #1234 (resolved by #5678) * @user1 @user2 @user3 reviewed pull requests -**Full Changelog**: https://github.com/modelcontextprotocol/csharp-sdk/compare/previous-tag...new-tag +**Full Changelog**: https://github.com/modelcontextprotocol/csharp-sdk/compare/{previous-tag}...v{version} + ``` diff --git a/.github/skills/publish-release/SKILL.md b/.github/skills/publish-release/SKILL.md index fce598c4d..0706cd361 100644 --- a/.github/skills/publish-release/SKILL.md +++ b/.github/skills/publish-release/SKILL.md @@ -8,6 +8,8 @@ compatibility: Requires gh CLI with repo access and GitHub API access for PR det Create a GitHub release for the `modelcontextprotocol/csharp-sdk` repository after a **prepare-release** PR has been merged. This skill refreshes the release notes to include any PRs merged between the preparation branch point and the merge, warns about changes that affect the version or breaking change assessment, and creates a **draft** GitHub release. +Use the shared [release branch reference](../shared-resources/release-branches.md) for branch roles, previous-release lookup rules, and release work-branch naming. + > **Safety: This skill only creates and updates draft releases. It must never publish a release.** If the user asks to publish, decline and instruct them to publish manually through the GitHub UI. ## Process @@ -18,7 +20,7 @@ Work through each step sequentially. Present findings at each step and get user The user may provide: - **A PR number or URL** — use directly -- **A version number** (e.g., `1.1.0`) — search for a merged PR titled `Release v{version}` +- **A version number** (e.g., `1.1.0`, `2.0.0-preview.1`) — search for a merged PR titled `Release v{version}`. Prerelease versions are used verbatim, for example `Release v2.0.0-preview.1` - **No context** — list recently merged PRs with `Release v` in the title and ask the user to select Verify the PR is merged. Extract: @@ -28,13 +30,13 @@ Verify the PR is merged. Extract: ### Step 2: Determine Version and Commit Range -1. Read `src/Directory.Build.props` at the merge commit to confirm ``. The tag is `v{VersionPrefix}`. -2. Determine the previous release tag from `gh release list` (most recent **published** release — exclude drafts with `--exclude-drafts`). +1. Read `src/Directory.Build.props` at the merge commit to confirm `` and ``. The tag is `v{VersionPrefix}` plus `-{VersionSuffix}` when the suffix is present; for example, `2.0.0` + `preview.1` → `v2.0.0-preview.1`. +2. Determine the previous release tag from `gh release list` (most recent **published** release — exclude drafts with `--exclude-drafts`). The lookup is branch-aware: when the merge commit is on a `release/{MAJOR}.x` branch, restrict candidates to tags matching `v{MAJOR}.*`; on `main`, use the most recent published release globally. See [release-branches.md](../shared-resources/release-branches.md). 3. Identify the full commit range: previous release tag → merge commit. ### Step 3: Check for Additional PRs -Compare the PRs included in the original prepare-release PR description with the full set of PRs now merged in the commit range. Use the [SemVer assessment guide](../bump-version/references/semver-assessment.md) (owned by the **bump-version** skill) to evaluate the impact of any new PRs against the version that was committed during preparation. +Compare the PRs included in the original prepare-release PR description with the full set of PRs now merged in the commit range. Use the [SemVer assessment guide](../bump-version/references/semver-assessment.md) (owned by the **bump-version** skill) to evaluate the impact of any new PRs against the version that was committed during preparation, including its prerelease and branch-context computation rules. This is not a policy change; only the version computation and previous-release lookup change. 1. Extract the PR list from the prepare-release PR description (all `#NNN` references in release notes sections). 2. Get the full set of PRs merged between the previous release tag and the merge commit. @@ -107,11 +109,11 @@ Follow [references/formatting.md](references/formatting.md) when composing and u ### Step 9: Create Draft Release Display release metadata for user review: -- **Title / Tag**: the confirmed version (e.g. `v1.1.0`) -- **Target**: merge commit SHA, its message, and the prepare-release PR link +- **Title / Tag**: the confirmed tag, including any prerelease suffix (e.g. `v1.3.1`, `v2.0.0-preview.1`) +- **Target**: merge commit SHA, its message, the merge commit's branch (the prepare-release PR base), and the prepare-release PR link After confirmation: -- Create with `gh release create --draft` (always `--draft`) +- Create with `gh release create --draft {tag} --target {merge-commit-branch}` (always `--draft`), using the prerelease tag verbatim when present - **Never publish.** If the user asks to publish, decline and instruct them to publish manually. When the user requests revisions after the initial creation, always rewrite the complete body as a file — never perform in-place string replacements. See [references/formatting.md](references/formatting.md). @@ -131,7 +133,7 @@ When the user requests revisions after the initial creation, always rewrite the ## Release Notes Template -Omit empty sections. The preamble is **always required** — it is not inside a section heading. +Omit empty sections. The preamble is **always required** — it is not inside a section heading. Tags may include prerelease suffixes, such as `v2.0.0-preview.1`, and Full Changelog compare links should use the exact tag. ```markdown [Preamble — REQUIRED. Summarize the release theme.] @@ -166,5 +168,6 @@ Refer to the [C# SDK Versioning](https://csharp.sdk.modelcontextprotocol.io/vers * @user submitted issue #1234 (resolved by #5678) * @user1 @user2 @user3 reviewed pull requests -**Full Changelog**: https://github.com/modelcontextprotocol/csharp-sdk/compare/previous-tag...new-tag +**Full Changelog**: https://github.com/modelcontextprotocol/csharp-sdk/compare/{previous-tag}...v{version} + ``` diff --git a/.github/skills/shared-resources/release-branches.md b/.github/skills/shared-resources/release-branches.md new file mode 100644 index 000000000..477567793 --- /dev/null +++ b/.github/skills/shared-resources/release-branches.md @@ -0,0 +1,40 @@ +# Release Branches + +Shared reference for release skills. Describes the branch roles used by the release workflow and the rules each skill follows for selecting a branch and looking up the previous release. + +## Branch roles + +| Branch | Purpose | CI behavior | +| ------------------- | ----------------------------------------------- | -------------------------------------- | +| `main` | Next-MAJOR preview/development line | Nightly `cron` build → GitHub Packages | +| `release/{MAJOR}.x` | Long-lived servicing branch for a shipped MAJOR | Every push → GitHub Packages | +| `release-{version}` | Short-lived release preparation branch | Built by PR CI; no package publishing | + +Official NuGet.org publishes happen only when a GitHub Release is created from a branch's tag. + +## Selecting a source/base branch (`prepare-release` Step 1) + +1. List candidate branches via: + `gh api repos/{owner}/{repo}/branches --paginate --jq '[.[] | select(.name == "main" or (.name | startswith("release/"))) | .name]'` +2. Present the list to the user. Default selection: `main`. +3. The selected branch drives: + - Previous-release lookup (see below). + - The branch on which the candidate version is read from `src/Directory.Build.props`. + - The commit range from which PRs are collected. + - The `--base` of the PR created at the end of the skill. + +## Previous-release tag lookup + +- On `main`: most recent published release globally (use `gh release list --exclude-drafts --limit 50` and pick the highest semver). No MAJOR filter. +- On `release/{MAJOR}.x`: most recent published release whose tag matches `v{MAJOR}.*`. Drafts are excluded. + +This is purely a baseline-selection rule. It does **not** change the breaking-change policy. See [the versioning docs](https://csharp.sdk.modelcontextprotocol.io/versioning.html) for the policy. + +## Work-branch naming + +Prepare-release work branches are named `release-{version}` (flat, hyphen-separated): +- `release-2.0.0-preview.1` +- `release-1.3.1` +- `release-2.0.0` + +Hyphens in prerelease versions are valid in git branch names. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c4842ba0..eca7a315a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,19 +1,24 @@ # Publish new package versions of ModelContextProtocol # -# Daily and Manual Runs -# - Triggered automatically at 07:00 UTC daily -# - Triggered manually using GitHub Actions workflow_dispatch event -# - Version prefix applied from /src/Directory.Build.props -# - Version suffix set to `ci.{github.run_number}` -# - Package published to GitHub package registry +# Triggers +# - Scheduled (07:00 UTC daily, main branch): produces a CI-suffixed package +# and publishes it to the GitHub package registry. # -# Official Releases -# - Triggered after a GitHub Release is created -# - Version prefix applied from /src/Directory.Build.props -# - Version suffix applied from /src/Directory.Build.props -# - Package published to GitHub package registry -# - Package published to NuGet.org -# - Version prefix and/or suffix should be updated after each release +# - Push to `release/**`: every commit to a release branch produces a CI-suffixed +# package and publishes it to the GitHub package registry. +# +# - Manual `workflow_dispatch` (any branch): same outputs as a scheduled/push build; +# accepts a `version_suffix_override` input. +# +# - GitHub Release published (any branch's tag): publishes the version from +# `src/Directory.Build.props` to the GitHub package registry AND NuGet.org. +# +# Version +# - Version prefix and suffix come from `src/Directory.Build.props`. +# - For non-release triggers, the suffix is replaced with `ci.{run_number}` +# (or `workflow_dispatch.inputs.version_suffix_override` when provided). +# - The prefix and suffix in `Directory.Build.props` should be updated after each +# release using the `bump-version` or `prepare-release` skills. name: Release Publishing @@ -21,6 +26,10 @@ on: schedule: - cron: '0 7 * * *' + push: + branches: + - 'release/**' + workflow_dispatch: inputs: version_suffix_override: diff --git a/ModelContextProtocol.slnx b/ModelContextProtocol.slnx index 1090c5377..6d096658d 100644 --- a/ModelContextProtocol.slnx +++ b/ModelContextProtocol.slnx @@ -1,11 +1,12 @@ - + + + -