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
6 changes: 6 additions & 0 deletions docs/release-management/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ New to Magpie? The [quick start](../quick-start.md) walks the whole path in
one place — install, the first `/magpie-setup:setup` run, and a recording of it
happening — plus the other agents and the secure-isolation setup to run next.

The first release-management skill you run opens with a one-minute
[welcome guide](../../plugins/magpie-setup/skills/setup/setup_preflight/guides/release-management.md):
the typical flow, what needs configuring, what to ask it, and how to make it yours.
Dismiss it, or hide it for good, from its last line;
*"show the release-management welcome again"* or `/magpie-setup:welcome-guide release-management` brings it back.

### Before the first run

<!-- BEGIN generated: skill-config (tools/dev/check-skill-config.py --fix) -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->

# 🐦 Magpie here — your release-management sidekick

A one-minute tour, then straight back to what you asked for.

## 🔄 The typical flow

1. 📋 **Prepare** the release plan and version bump — *"plan our next release"*
2. 🔑 **Check KEYS** before RC signing — *"sync the release signing key"*
3. 📦 **Cut an RC** and prepare signing/staging commands — *"cut rc1 for this version"*
4. ✅ **Verify the RC** before a vote — *"verify the staged release candidate"*
5. 🗳️ **Draft the vote**, then tally results — *"draft the vote email"* / *"tally the votes"*
6. 🚀 **Promote** a release only after approval — *"prepare promotion commands"*
7. 📣 **Draft the announcement**, then review archive cleanup — *"draft the announcement"*
8. 📊 **Audit** the completed release evidence — *"prepare the release audit report"*

These steps are guided workflows, not autonomous publishing.
The Release Manager keeps the signing key and runs signing and publication commands.
Vote and announcement emails are drafts, never automatically sent.
Review and explicitly approve proposed external or repository changes.

## ⚙️ Configure it

Release skills use project-specific configuration:

- ⚙️ **Release policy** — `release-management-config.md` for planning, votes, announcements, archives, audit reports and KEYS
- 📦 **Build and staging** — `release-build.md` for `rc-cut` and `verify-rc`
- 🗳️ **Approval roster** — `pmc-roster.md` for `vote-tally` and `promote`
- 🚆 **Release trains** — `release-trains.md` for `prepare` and `archive-sweep`

The distribution, approval and announcement backends are set in `release-management-config.md`.
Configure personally with `/magpie-setup:setup config`.
Team adoption with `/magpie-setup:setup adopt` is a maintainer decision.

## 💬 Just ask

Talk to Magpie in plain language:

- *"prepare the release plan for our next version"*
- *"check that my public signing key is in KEYS"*
- *"give me the commands to cut and verify rc1"*
- *"draft a vote email for this verified RC"*
- *"tally the release vote and prepare the result draft"*
- *"prepare promotion and announcement after the vote passes"*
- *"review old release archives and generate an audit report"*

## 🎨 Make it yours

Describe your release policy and preferred backend:

- *"use a 96-hour vote window instead of 72"*
- *"always run the KEYS check before cutting an RC"*

Review proposed configuration or override changes before applying them.
Useful elsewhere? Ask *"upstream this change to Magpie"* to prepare a contribution for review.

<!-- welcome footer: generated from guides/_footer.md -->
48 changes: 46 additions & 2 deletions tools/setup-preflight/tests/test_welcome.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,17 @@

REPO_ROOT = Path(__file__).resolve().parents[3]
SECURITY_SKILL = "issue-sync"
OTHER_SKILL = "rc-cut"
# Every shipped family now has a guide, so the "family without a guide"
# cases use a synthetic one registered for the duration of each test.
OTHER_SKILL = "example-guideless-skill"
OTHER_FAMILY = "example-guideless-family"


@pytest.fixture
def guideless_family(monkeypatch: pytest.MonkeyPatch) -> None:
from setup_preflight import families

monkeypatch.setitem(families.SKILL_FAMILIES, OTHER_SKILL, OTHER_FAMILY)


def stamp(root: Path) -> dict:
Expand Down Expand Up @@ -207,6 +217,28 @@ def test_the_setup_family_guide_comes_on_the_next_run(project: Path, version) ->
assert due(project, "status") == [("general", "again")]


def test_the_release_management_family_guide_comes_on_the_next_run(project: Path, version) -> None:
for skill in (
"announce-draft",
"archive-sweep",
"audit-report",
"keys-sync",
"prepare",
"promote",
"rc-cut",
"verify-rc",
"vote-draft",
"vote-tally",
):
assert welcome.family_of(skill) == "release-management"

assert due(project, "rc-cut") == [("general", "first-run")]
welcome.record(project, "general", "seen")
assert due(project, "rc-cut") == [("release-management", "first-run")]
welcome.record(project, "release-management", "seen")
assert due(project, "rc-cut") == [("general", "again")]


def test_at_most_one_guide_per_run(project: Path, version) -> None:
for _ in range(8):
found = welcome_findings(project, SECURITY_SKILL)
Expand All @@ -229,8 +261,10 @@ def test_a_hidden_general_guide_does_not_hold_back_the_family_one(project: Path,
assert due(project) == [("security", "first-run")]


@pytest.mark.usefixtures("guideless_family")
def test_a_skill_of_a_family_without_a_guide_gets_only_the_general_one(project: Path, version) -> None:
assert welcome.family_of(OTHER_SKILL) == "release-management"
assert welcome.family_of(OTHER_SKILL) == OTHER_FAMILY
assert OTHER_FAMILY not in welcome.family_guides()
assert due(project, OTHER_SKILL) == [("general", "first-run")]
welcome.record(project, "general", "never")
assert due(project, OTHER_SKILL) == []
Expand Down Expand Up @@ -293,6 +327,7 @@ def test_no_welcome_leaves_it_out(project: Path, capsys, version) -> None:
# --- the three answers ----------------------------------------------------------------


@pytest.mark.usefixtures("guideless_family")
def test_seen_shows_again_and_stops_after_three(project: Path, version) -> None:
reasons = []
for _ in range(5):
Expand All @@ -311,6 +346,7 @@ def test_a_later_answer_replaces_seen_without_counting_twice(project: Path, vers
assert entry["shown"] == 1


@pytest.mark.usefixtures("guideless_family")
def test_dismiss_hides_it_until_the_version_changes(project: Path, version) -> None:
welcome.record(project, "general", "dismiss")
assert codes(project, OTHER_SKILL) == []
Expand All @@ -319,12 +355,14 @@ def test_dismiss_hides_it_until_the_version_changes(project: Path, version) -> N
assert [f.facts["reason"] for f in found] == ["upgraded"]


@pytest.mark.usefixtures("guideless_family")
def test_never_persists_across_versions(project: Path, version) -> None:
welcome.record(project, "general", "never")
version[0] = "1.0.0"
assert codes(project, OTHER_SKILL) == []


@pytest.mark.usefixtures("guideless_family")
def test_enable_clears_the_choice(project: Path, version) -> None:
welcome.record(project, "general", "never")
assert welcome.enable(project, "general") is True
Expand Down Expand Up @@ -515,6 +553,7 @@ def test_the_rules_section_and_all_guides_ship() -> None:
"contributor-growth",
"repo-health",
"setup",
"release-management",
} <= set(welcome.guides())
assert welcome.family_guides() == [g for g in welcome.guides() if g != "general"]

Expand All @@ -532,6 +571,7 @@ def test_the_rules_section_and_all_guides_ship() -> None:
"contributor-growth",
"repo-health",
"setup",
"release-management",
],
)
def test_each_guide_is_short_and_ends_with_the_way_back(name: str) -> None:
Expand Down Expand Up @@ -562,6 +602,7 @@ def test_the_footer_is_shared_and_filled_in_per_guide() -> None:
contributor_growth = welcome.guide("contributor-growth") or ""
repo_health = welcome.guide("repo-health") or ""
setup = welcome.guide("setup") or ""
release_management = welcome.guide("release-management") or ""
for text in (
security,
general,
Expand All @@ -573,6 +614,7 @@ def test_the_footer_is_shared_and_filled_in_per_guide() -> None:
contributor_growth,
repo_health,
setup,
release_management,
):
assert "{{" not in text
assert "welcome footer: generated" not in text
Expand All @@ -596,6 +638,8 @@ def test_the_footer_is_shared_and_filled_in_per_guide() -> None:
assert "--family repo-health --color=always" in repo_health
assert "`/magpie-setup:welcome-guide setup`" in setup
assert "--family setup --color=always" in setup
assert "`/magpie-setup:welcome-guide release-management`" in release_management
assert "--family release-management --color=always" in release_management


def test_list_prints_every_guide(capsys) -> None:
Expand Down
2 changes: 1 addition & 1 deletion tools/skill-evals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Behavioral eval harness for Apache Magpie skills. Each eval suite tests a skill

Suites are currently implemented for:

- **setup** — 140 cases across 30 steps (step-verify-drift, step-overrides-surface, step-override-bypass, step-m3-baseline-pick, step-m4-install-gates, step-m5-no-repo-offer, step-m5-companion-offer, step-upgrade-companion-offer, step-adopt-settings-merge, verify-default-set, uninstall-default-set, lock-marketplace-parse, adopt-write-floor, setup-prefill-from-floor, preflight-floor, upgrade-adoption-split, verify-floor, adopt-review-process, adopt-security-list, step-reconcile, step-verify, step-config-stamp, step-config-adversarial, step-config-mail, step-adopt-stamp, step-upgrade-stamp, step-delegate-propose, config-vcs-backend, config-local-custom-parser)
- **setup** — 141 cases across 30 steps (step-verify-drift, step-overrides-surface, step-override-bypass, step-m3-baseline-pick, step-m4-install-gates, step-m5-no-repo-offer, step-m5-companion-offer, step-upgrade-companion-offer, step-adopt-settings-merge, verify-default-set, uninstall-default-set, lock-marketplace-parse, adopt-write-floor, setup-prefill-from-floor, preflight-floor, upgrade-adoption-split, verify-floor, adopt-review-process, adopt-security-list, step-reconcile, step-verify, step-config-stamp, step-config-adversarial, step-config-mail, step-adopt-stamp, step-upgrade-stamp, step-delegate-propose, config-vcs-backend, config-local-custom-parser)
- **setup-isolated-setup-install** — 19 cases across 5 steps (runtime-routing, step-snapshot-drift, step-scope-confirm, step-hardware-key, step-roborev-exclusion)
- **setup-privacy-llm** — 6 cases across 2 steps (step-1-resolve, step-4-gate)
- **setup-shared-config-sync** — 12 cases across 2 steps (step-3-decide-action, step-5-draft-commit)
Expand Down
4 changes: 2 additions & 2 deletions tools/skill-evals/evals/setup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

Behavioral evals for the `setup` skill.

## Suites (140 cases total)
## Suites (141 cases total)

| Suite | Step | Cases | What it covers |
|---|---|---|---|
Expand All @@ -25,7 +25,7 @@ Behavioral evals for the `setup` skill.
| adopt-review-process | adopt.md § Step 4c | 5 | a fresh adopt finding two deviations in the confirmed documents where one is accepted and one rejected, and a release policy left alone because that family is not in the floor; a re-adoption reviewing only the one family the floor diff added; a re-adoption that adds no family, where edited process documents are still not re-read; a sentence that would weaken a merge confirmation gate, dropped and named while a label deviation beside it is written; and a repo whose only candidate document the maintainer un-ticks, skipping the step |
| adopt-security-list | adopt.md § Step 4b | 4 | an ASF project whose dedicated security list is in its docs and on the ASF page (recommend `security@`); an ASF project with no dedicated list (no recommendation, `private@` only as a flagged fallback); a non-ASF project, which is not asked; and conflicting evidence between `SECURITY.md` and the ASF page (no recommendation, flagged fallback) |
| setup-prefill-from-floor | install.md § Step M0b | 4 | adopted (propose the floor), unadopted (framework defaults), a foreign marketplace called out, and a snapshot lock falling through |
| preflight-floor | preflight-block.md § Pre-flight | 20 | at floor (silent), below floor (update), plugin missing (install), foreign marketplace (ask first, run nothing), no `claude` CLI (print), unadopted (propose setup), `apache/magpie` in an alarming context (update anyway, unasked), unreadable `claude plugin list --json` (silent, treated as unknown not absent), first Magpie run (show the general welcome guide, record it, carry on), first utilities run (show the utilities welcome guide, record it, carry on), first mentoring run (show the mentoring welcome guide, record it, carry on), first pairing run (show the pairing welcome guide, record it, carry on), first issue run (show the issue welcome guide, record it, carry on), first pr-management run (show the pr-management welcome guide, record it, carry on), first contributor-growth run (show the contributor-growth welcome guide, record it, carry on), below floor on Codex (marketplace upgrade), below floor on Gemini (update the extension once), at floor on Gemini (silent), first repo-health run (show the repo-health welcome guide, record it, carry on), first setup run (show the setup welcome guide, record it, carry on) |
| preflight-floor | preflight-block.md § Pre-flight | 21 | at floor (silent), below floor (update), plugin missing (install), foreign marketplace (ask first, run nothing), no `claude` CLI (print), unadopted (propose setup), `apache/magpie` in an alarming context (update anyway, unasked), unreadable `claude plugin list --json` (silent, treated as unknown not absent), first Magpie run (show the general welcome guide, record it, carry on), first utilities run (show the utilities welcome guide, record it, carry on), first mentoring run (show the mentoring welcome guide, record it, carry on), first pairing run (show the pairing welcome guide, record it, carry on), first issue run (show the issue welcome guide, record it, carry on), first pr-management run (show the pr-management welcome guide, record it, carry on), first contributor-growth run (show the contributor-growth welcome guide, record it, carry on), below floor on Codex (marketplace upgrade), below floor on Gemini (update the extension once), at floor on Gemini (silent), first repo-health run (show the repo-health welcome guide, record it, carry on), first setup run (show the setup welcome guide, record it, carry on), first release-management run (show the release-management welcome guide, record it, carry on) |
| upgrade-adoption-split | upgrade.md § Step 0b | 4 | not adopted (nothing staged), adopted (floor raised and staged), already ahead (floor unchanged), snapshot method (falls through) |
| verify-floor | verify.md § Adoption floor | 4 | no lock (not a fault), ahead of floor with extra plugins (not a fault), a shortfall (a fault), a floor plugin the marketplace no longer ships (a fault, not installed around) |
| step-reconcile | reconcile.md § The sweep | 3 | a clean sweep on a pinned-snapshot install (anchor present, config resolved — stamp written, nothing proposed), a renamed step heading stranding an override's anchor (one re-anchor proposal named), a marketplace install whose plugin cache is sandbox-denied (anchor resolution left `unchecked`, config resolution still completes) |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"tags":["local-smoke"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"action": "welcome", "commands": [], "blocks": false}
Loading
Loading