From 338f357f555449386eb69206fbabf8d7442f328d Mon Sep 17 00:00:00 2001 From: Drew Cain Date: Thu, 11 Jun 2026 01:01:21 -0500 Subject: [PATCH] chore(ci): route release recipes through PRs and refresh release runbook main's ruleset now rejects direct pushes, which broke 'just release' mid-flight for v0.22.0. The release/beta recipes now land the version bump via a rebase-merged release PR and tag the rebased commit (found by commit subject). Adds a changelog-on-main pre-flight check, and updates AGENTS.md and the release runbook to match, including the docs.basicmemory.com steps which still described the old src/pages site. Signed-off-by: Drew Cain --- .claude/commands/release/release.md | 52 ++++++++++------ AGENTS.md | 8 ++- justfile | 96 +++++++++++++++++++++-------- 3 files changed, 109 insertions(+), 47 deletions(-) diff --git a/.claude/commands/release/release.md b/.claude/commands/release/release.md index 33cbd541..808b095c 100644 --- a/.claude/commands/release/release.md +++ b/.claude/commands/release/release.md @@ -28,7 +28,10 @@ You are an expert release manager for the Basic Memory project. When the user ru #### Documentation Validation 1. **Changelog Check** - - CHANGELOG.md contains entry for target version + - CHANGELOG.md contains entry for target version **already landed on `main`** + (main only accepts changes via PR, so the changelog entry must go through + its own PR before running the release; the recipe pre-flight-checks for a + `## vX.Y.Z` heading) - Entry includes all major features and fixes - Breaking changes are documented @@ -41,11 +44,15 @@ just release The justfile target handles: - ✅ Version format validation - ✅ Git status and branch checks -- ✅ Quality checks (`just check` - lint, format, type-check, tests) +- ✅ Changelog entry check (must already be on `main`) +- ✅ Quality checks (`just lint` + `just typecheck`) - ✅ Version update across all consolidated manifests via `just set-version` (Python package + Claude Code plugin/marketplaces + Codex plugin + Hermes + OpenClaw) -- ✅ Automatic commit with proper message -- ✅ Tag creation and pushing to GitHub +- ✅ Release PR: commits the bump on a `release/vX.Y.Z` branch, opens a PR + (`chore(core): release vX.Y.Z`), and rebase-merges it — the `main` ruleset + rejects direct pushes and the repo disallows merge commits +- ✅ Tags the rebased bump commit on `main` (found by commit subject, since + the rebase rewrites the SHA) and pushes the tag - ✅ Release workflow trigger (automatic on tag push) The GitHub Actions workflow (`.github/workflows/release.yml`) then: @@ -127,25 +134,30 @@ After PyPI release is published, update the MCP registry: - **Deploy**: Follow deployment process for basicmachines.co **2. docs.basicmemory.com** (`/Users/drew/code/docs.basicmemory.com`) - - **Goal**: Add new release notes section to the latest-releases page - - **File**: `src/pages/latest-releases.mdx` + - **Goal**: Add a What's New page for the release and bump the homepage badge + - **Site shape**: Nuxt/Docus content site. The changelog page + (`content/2.whats-new/*.changelog.md`) auto-fetches GitHub releases — no + manual changelog update needed. See that repo's CLAUDE.md "Version Bump + Checklist". - **What to do**: 1. Pull latest from GitHub: `git pull origin main` 2. Create release branch: `git checkout -b release/v{VERSION}` - 3. Read the existing file to understand the format and structure - 4. Read `/Users/drew/code/basic-memory/CHANGELOG.md` to get release content - 5. Add new release section **at the top** (after MDX imports, before other releases) - 6. Follow the existing pattern: - - Heading: `## [v{VERSION}](github-link) — YYYY-MM-DD` - - Focus statement if applicable - - `` block with highlights (3-5 key items) - - Sections for Features, Bug Fixes, Breaking Changes, etc. - - Link to full changelog at the end - - Separator `---` between releases - 7. Commit changes: `git commit -m "docs: add v{VERSION} release notes"` - 8. Push branch: `git push origin release/v{VERSION}` - - **Source content**: Extract and format sections from CHANGELOG.md for this version - - **Deploy**: Follow deployment process for docs.basicmemory.com + 3. Read `/Users/drew/code/basic-memory/CHANGELOG.md` to get release content + 4. **New minor/major release**: add `content/2.whats-new/1.v{VERSION}.md` + modeled on the previous version page (frontmatter title/description, + headline feature first, then sections, then an Upgrading note) and + renumber the existing what's-new pages down one slot (URLs don't + change — Nuxt strips the numeric prefixes) + 5. **Patch release**: append a short note to the current version's page + instead of creating a new one + 6. Update the homepage version badge in `content/index.md` (the + `v0.XX →` button text and its `to: /whats-new/v{VERSION}` link) + 7. If the release adds user-facing features, update the relevant guide + and reference pages (`content/3.cloud/`, `content/9.reference/`) + 8. Commit: `git commit -s -m "docs: add v{VERSION} release notes"` + 9. Push branch and open a PR; merge after the release is tagged + - **Deploy**: push to main auto-deploys to development; production requires + manual workflow dispatch via GitHub Actions **4. Announce Release** - Post to Discord community if significant changes diff --git a/AGENTS.md b/AGENTS.md index 80d00bc0..f6ac7e0e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -300,7 +300,9 @@ See SPEC-16 for full context manager refactor details. ### Release Process -Releases are driven by `just release` / `just beta` — never by a bare `git tag`. The recipes bump version metadata, run pre-flight checks, commit, tag, and push. GitHub Actions then publishes to PyPI and updates the Homebrew formula. +Releases are driven by `just release` / `just beta` — never by a bare `git tag`. The recipes bump version metadata, run pre-flight checks, land the bump on `main` through a release PR, tag, and push the tag. GitHub Actions then publishes to PyPI and updates the Homebrew formula. + +**Main requires PRs.** The `main` ruleset rejects direct pushes ("Changes must be made through a pull request") and the repo disallows merge commits, so the recipes push a `release/vX.Y.Z` branch, open a PR titled `chore(core): release vX.Y.Z`, rebase-merge it with `gh pr merge --rebase`, then tag the rebased bump commit on `main` (located by its commit subject, since rebasing rewrites the SHA) and push the tag. The CHANGELOG entry for the version must already be on `main` — land it via a normal PR before running the recipe (it pre-flight-checks for a `## vX.Y.Z` heading). **Stable release:** @@ -308,7 +310,7 @@ Releases are driven by `just release` / `just beta` — never by a bare `git tag just release v0.21.3 ``` -The recipe runs `just lint` + `just typecheck`, then updates every release manifest through `scripts/update_versions.py`: `src/basic_memory/__init__.py`, `server.json`, the root Claude marketplace, the Claude Code plugin manifest and local marketplace, the Hermes `plugin.yaml`, and the OpenClaw `package.json`. It commits as `chore: update version to X.Y.Z for vX.Y.Z release`, creates the `vX.Y.Z` tag, and pushes both the commit and the tag to `origin/main`. After the tag lands, the `Release` workflow builds the Python package, publishes to PyPI, creates the GitHub release with auto-generated notes, publishes the OpenClaw npm package, and updates the Homebrew formula. The recipe finishes by printing the post-release tasks the workflow doesn't cover. +The recipe runs `just lint` + `just typecheck`, then updates every release manifest through `scripts/update_versions.py`: `src/basic_memory/__init__.py`, `server.json`, the root Claude marketplace, the Claude Code plugin manifest and local marketplace, the Hermes `plugin.yaml`, and the OpenClaw `package.json`. It commits as `chore: update version to X.Y.Z for vX.Y.Z release` on a `release/vX.Y.Z` branch, lands it on `main` via a rebase-merged PR, then tags the rebased commit and pushes the tag. After the tag lands, the `Release` workflow builds the Python package, publishes to PyPI, creates the GitHub release with auto-generated notes, publishes the OpenClaw npm package, and updates the Homebrew formula. The recipe finishes by printing the post-release tasks the workflow doesn't cover. **Beta release:** `just beta v0.21.3b1` — same flow with a beta-suffixed tag. PyPI consumers install with `pip install basic-memory --pre`. @@ -319,7 +321,7 @@ The recipe runs `just lint` + `just typecheck`, then updates every release manif **Do not tag releases by hand.** A bare `git tag vX.Y.Z` skips the in-code version bump. Package metadata is still correct (uv-dynamic-versioning derives it from the git tag) but `basic-memory --version` reports the previous release, which is what happened with v0.21.2 → v0.21.3. **Post-release tasks** the recipe surfaces but doesn't run: -- `docs.basicmemory.com` — add notes to `src/pages/latest-releases.mdx` +- `docs.basicmemory.com` — add a What's New page under `content/2.whats-new/` and bump the version badge in `content/index.md` (the changelog page auto-fetches GitHub releases; see that repo's CLAUDE.md version-bump checklist) - `basicmachines.co` — bump version in `src/components/sections/hero.tsx` - MCP Registry — `mcp-publisher publish` from the repo root diff --git a/justfile b/justfile index ddade952..9e820ca9 100644 --- a/justfile +++ b/justfile @@ -383,17 +383,31 @@ release version: echo "❌ Tag {{version}} already exists" exit 1 fi - + + # Changelog must already be on main (land it via a normal PR first) + if ! grep -q "^## {{version}} " CHANGELOG.md; then + echo "❌ CHANGELOG.md has no entry for {{version}}. Land one via PR first." + exit 1 + fi + # Run quality checks echo "🔍 Running lint checks..." just lint just typecheck - + # Update all package manifests to the one Basic Memory product version. echo "📝 Updating consolidated package versions..." just set-version "{{version}}" - # Commit version update + # Trigger: main's ruleset rejects direct pushes ("Changes must be made + # through a pull request"). + # Why: the version bump must land on main before the tag is cut, so it + # rides a release PR that is rebase-merged (the repo disallows merge + # commits). + # Outcome: the bump commit gets a new SHA on main; the tag is created on + # that rebased commit, found by its commit subject. + COMMIT_SUBJECT="chore: update version to $VERSION_NUM for {{version}} release" + git checkout -b "release/{{version}}" git add \ src/basic_memory/__init__.py \ server.json \ @@ -404,22 +418,35 @@ release version: integrations/hermes/plugin.yaml \ integrations/hermes/__init__.py \ integrations/openclaw/package.json - git commit -s -m "chore: update version to $VERSION_NUM for {{version}} release" - - # Create and push tag - echo "🏷️ Creating tag {{version}}..." - git tag "{{version}}" - - echo "📤 Pushing to GitHub..." - git push origin main + git commit -s -m "$COMMIT_SUBJECT" + + echo "📤 Opening release PR..." + git push -u origin "release/{{version}}" + gh pr create --title "chore(core): release {{version}}" \ + --body "Version bump for {{version}}. See CHANGELOG.md for release notes." + gh pr merge "release/{{version}}" --rebase --delete-branch + + git checkout main + git pull --ff-only origin main + + # Tag the rebased bump commit, wherever it landed on main + TAG_COMMIT=$(git log origin/main --fixed-strings --grep "$COMMIT_SUBJECT" --format='%H' -1) + if [[ -z "$TAG_COMMIT" ]]; then + echo "❌ Could not find the version bump commit on main. Tag manually." + exit 1 + fi + + echo "🏷️ Creating tag {{version}} at $TAG_COMMIT..." + git tag "{{version}}" "$TAG_COMMIT" git push origin "{{version}}" - + echo "✅ Release {{version}} created successfully!" echo "📦 GitHub Actions will build and publish to PyPI" echo "🔗 Monitor at: https://github.com/basicmachines-co/basic-memory/actions" echo "" echo "📝 REMINDER: Post-release tasks:" - echo " 1. docs.basicmemory.com - Add release notes to src/pages/latest-releases.mdx" + echo " 1. docs.basicmemory.com - Add a What's New page under content/2.whats-new/" + echo " and bump the badge in content/index.md (see that repo's CLAUDE.md)" echo " 2. basicmachines.co - Update version in src/components/sections/hero.tsx" echo " 3. MCP Registry - Run: mcp-publisher publish" echo " See: .claude/commands/release/release.md for detailed instructions" @@ -467,7 +494,15 @@ beta version: echo "📝 Updating consolidated package versions..." just set-version "{{version}}" - # Commit version update + # Trigger: main's ruleset rejects direct pushes ("Changes must be made + # through a pull request"). + # Why: the version bump must land on main before the tag is cut, so it + # rides a release PR that is rebase-merged (the repo disallows merge + # commits). + # Outcome: the bump commit gets a new SHA on main; the tag is created on + # that rebased commit, found by its commit subject. + COMMIT_SUBJECT="chore: update version to $VERSION_NUM for {{version}} beta release" + git checkout -b "release/{{version}}" git add \ src/basic_memory/__init__.py \ server.json \ @@ -478,23 +513,36 @@ beta version: integrations/hermes/plugin.yaml \ integrations/hermes/__init__.py \ integrations/openclaw/package.json - git commit -s -m "chore: update version to $VERSION_NUM for {{version}} beta release" - - # Create and push tag - echo "🏷️ Creating tag {{version}}..." - git tag "{{version}}" - - echo "📤 Pushing to GitHub..." - git push origin main + git commit -s -m "$COMMIT_SUBJECT" + + echo "📤 Opening release PR..." + git push -u origin "release/{{version}}" + gh pr create --title "chore(core): release {{version}}" \ + --body "Version bump for {{version}} beta." + gh pr merge "release/{{version}}" --rebase --delete-branch + + git checkout main + git pull --ff-only origin main + + # Tag the rebased bump commit, wherever it landed on main + TAG_COMMIT=$(git log origin/main --fixed-strings --grep "$COMMIT_SUBJECT" --format='%H' -1) + if [[ -z "$TAG_COMMIT" ]]; then + echo "❌ Could not find the version bump commit on main. Tag manually." + exit 1 + fi + + echo "🏷️ Creating tag {{version}} at $TAG_COMMIT..." + git tag "{{version}}" "$TAG_COMMIT" git push origin "{{version}}" - + echo "✅ Beta release {{version}} created successfully!" echo "📦 GitHub Actions will build and publish to PyPI as pre-release" echo "🔗 Monitor at: https://github.com/basicmachines-co/basic-memory/actions" echo "📥 Install with: uv tool install basic-memory --pre" echo "" echo "📝 REMINDER: For stable releases, update documentation sites:" - echo " 1. docs.basicmemory.com - Add release notes to src/pages/latest-releases.mdx" + echo " 1. docs.basicmemory.com - Add a What's New page under content/2.whats-new/" + echo " and bump the badge in content/index.md (see that repo's CLAUDE.md)" echo " 2. basicmachines.co - Update version in src/components/sections/hero.tsx" echo " See: .claude/commands/release/release.md for detailed instructions"