Files
beads/.beads/formulas/beads-release.formula.toml
beads/crew/emma ba3a725299 fix(formula): add dev build handling to local-install step
The local-install step now includes instructions for developers who
build bd from source. This ensures all bd binaries in PATH are updated,
not just the release binary downloaded by the install script.

Executed-By: beads/crew/emma
Rig: beads
Role: crew
2026-01-25 21:33:59 -08:00

798 lines
19 KiB
TOML

# Beads Release Formula v2 (Gate-Aware)
#
# This formula uses gates for async waits during CI, enabling clean phase handoffs.
# Instead of polling, the polecat completes Phase 1, and a gate blocks Phase 2
# until GitHub Actions finishes.
#
# Phases:
# 1. Prep & Push: preflight → push-tag (polecat work)
# 2. Gate: await-ci (async wait for release.yml)
# 3. Verify: verify-github, verify-npm, verify-pypi (parallel)
# 4. Install: local-install → release-complete (fan-in)
formula = "beads-release"
description = """
Beads release workflow v2 - gate-aware async release.
This formula orchestrates a complete release cycle with async gates:
Phase 1 (Polecat Work):
1. Preflight checks (clean git, up to date)
2. Documentation updates (CHANGELOG, info.go)
3. Version bump (all components)
4. Git operations (commit, tag, push)
Gate (Async Wait):
5. await-ci: Gate on GitHub Actions release.yml completion
Phase 2 (Parallel Verification):
6. Verify GitHub release, npm package, PyPI package (concurrent)
Phase 3 (Installation):
7. Local installation update
8. Daemon restart
## Usage
```bash
bd mol wisp create beads-release --var version=0.44.0
```
Or assign to a polecat:
```bash
gt sling beads/polecats/p1 --formula beads-release --var version=0.44.0
```
The polecat will complete Phase 1, then signal phase-complete. The gate blocks
until release.yml finishes. A new polecat (or the same one) resumes for Phases 2-3.
"""
type = "workflow"
phase = "vapor" # Ensures this runs as a wisp - bd pour will warn, gt sling --formula creates wisp
version = 1
[vars.version]
description = "The semantic version to release (e.g., 0.44.0)"
required = true
# =============================================================================
# Phase 1: Prep & Push
# =============================================================================
[[steps]]
id = "preflight-worktree"
title = "Preflight: Verify git context"
description = """
Ensure we're in the correct git directory, especially for worktree setups.
```bash
# Check current repo root and remote
git rev-parse --show-toplevel
git remote get-url origin
# Verify this is the main worktree (not a linked worktree)
git worktree list
```
For worktree setups, releases should be done from the **main worktree** to ensure:
- Correct file paths in commits
- Proper tag association
- Clean branch history
If you're in a linked worktree:
1. Note the path to the main worktree (first line of `git worktree list`)
2. Switch to that directory before proceeding
3. Or use: `cd $(git worktree list | head -1 | cut -d' ' -f1)`
**Red flags:**
- Remote URL doesn't match expected repository
- You're in a linked worktree (not the main one)
- `git status` shows different files than expected
"""
[[steps]]
id = "preflight-git"
title = "Preflight: Check git status & auto-stash"
needs = ["preflight-worktree"]
description = """
Ensure working tree is clean before starting release.
```bash
git status --short
```
**Handling uncommitted changes:**
If changes are in release-related files (CHANGELOG, version files), you may want to:
- Include them in the release commit
- Or stash and apply after version bump
If changes are in **non-release files** (e.g., .beads/config.yaml, .claude/settings.json, local configs):
```bash
# Auto-stash non-release files
git stash push -m "pre-release: non-release changes" -- .beads/ .claude/ *.local* .env*
# Verify working tree is now clean (or only has release files)
git status --short
```
**Important:** The bump script may fail if non-release files are modified. Always stash:
- `.beads/` directory (local config)
- `.claude/` directory (agent settings)
- Any `.local`, `.env`, or personal config files
After release completes, restore with:
```bash
git stash pop
```
"""
[[steps]]
id = "preflight-pull"
title = "Preflight: Pull latest & verify sync"
needs = ["preflight-git"]
description = """
Ensure we're up to date with origin and branch is properly synced.
```bash
# Fetch latest from origin
git fetch origin
# Check branch status BEFORE pulling
git status -sb
```
**Branch sync verification:**
- "Your branch is behind" → Pull needed, proceed with `git pull --rebase`
- "Your branch is ahead" → You have unpushed commits - review before release!
- "Your branch has diverged" → **STOP** - resolve divergence first
```bash
# If branch is behind or even, pull latest
git pull --rebase
```
**Recovering from divergence:**
If your branch has diverged from origin (e.g., after a botched commit):
```bash
# Option 1: Reset to origin (loses local commits)
git reset --hard origin/main
# Option 2: Rebase local commits on top of origin
git rebase origin/main
# Option 3: Create a backup branch first
git branch backup-before-release
git reset --hard origin/main
```
**After pulling, verify sync:**
```bash
git status -sb
# Should show: "## main...origin/main" (no ahead/behind)
```
"""
[[steps]]
id = "detect-half-done-release"
title = "Detect half-done release"
needs = ["preflight-pull"]
description = """
Check if a previous release was started but not completed (version mismatch).
**Compare versions across all sources:**
```bash
# Last git tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "none")
echo "Last tag: $LAST_TAG"
# Version in code
CODE_VERSION=$(grep 'Version = ' cmd/bd/version.go | cut -d'"' -f2)
echo "Code version: $CODE_VERSION"
# Version in CHANGELOG (most recent versioned section)
CHANGELOG_VERSION=$(grep -E '## \\[[0-9]+\\.[0-9]+\\.[0-9]+\\]' CHANGELOG.md | head -1 | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+')
echo "CHANGELOG version: $CHANGELOG_VERSION"
# Version in npm package
NPM_VERSION=$(jq -r '.version' npm-package/package.json)
echo "npm version: $NPM_VERSION"
```
**Healthy state (ready for new release):**
- All versions match (e.g., all show 0.46.0)
- Last tag matches code version
**Half-done release detected if:**
- CHANGELOG shows {{version}} but code shows previous version
- Code version doesn't match last tag
- npm/PyPI versions are out of sync
**Recovery from half-done release:**
```bash
# If CHANGELOG was updated but code wasn't bumped:
# Either complete the release or revert CHANGELOG changes
# Check what state we're in
git diff $LAST_TAG -- CHANGELOG.md | head -50
```
If mismatch detected, either:
1. Complete the partial release (bump remaining files)
2. Revert partial changes and start fresh
3. Carefully verify what's already pushed vs local-only
"""
[[steps]]
id = "review-changes"
title = "Review changes since last release"
needs = ["detect-half-done-release"]
description = """
Understand what's being released.
```bash
git log $(git describe --tags --abbrev=0)..HEAD --oneline
```
Categorize changes:
- Features (feat:)
- Fixes (fix:)
- Breaking changes
- Documentation
"""
[[steps]]
id = "verify-changelog-complete"
title = "Verify CHANGELOG completeness"
needs = ["review-changes"]
description = """
Ensure CHANGELOG captures ALL commits since the last release.
**Step 1: Count commits since last tag**
```bash
LAST_TAG=$(git describe --tags --abbrev=0)
COMMIT_COUNT=$(git rev-list $LAST_TAG..HEAD --count)
echo "Commits since $LAST_TAG: $COMMIT_COUNT"
```
**Step 2: List all commits with their messages**
```bash
git log $LAST_TAG..HEAD --oneline --no-merges
```
**Step 3: Cross-reference with CHANGELOG**
Open CHANGELOG.md and verify every significant commit is documented in [Unreleased].
**Red flags for stale CHANGELOG:**
- If CHANGELOG was last modified days ago but there are recent commits
- If commit count seems high but [Unreleased] section is sparse
- If PR titles in commits don't match CHANGELOG entries
```bash
# Check when CHANGELOG was last modified
git log -1 --format="%ar" -- CHANGELOG.md
# Compare to latest commit date
git log -1 --format="%ar"
```
**If CHANGELOG is stale:**
1. Review all commits since last tag
2. Add missing entries to appropriate sections (Added/Changed/Fixed)
3. Group related changes
4. Focus on user-facing changes and breaking changes
Don't proceed until CHANGELOG is complete - it's the release notes!
"""
[[steps]]
id = "update-changelog"
title = "Update CHANGELOG.md"
needs = ["verify-changelog-complete"]
description = """
Write the [Unreleased] section with all changes for {{version}}.
Format: Keep a Changelog (https://keepachangelog.com)
Sections:
- ### Added
- ### Changed
- ### Fixed
- ### Documentation
The bump script will stamp the date automatically.
"""
[[steps]]
id = "update-info-go"
title = "Update info.go versionChanges"
needs = ["update-changelog"]
description = """
Add entry to versionChanges in cmd/bd/info.go.
This powers `bd info --whats-new` for agents.
```go
"{{version}}": {
"summary": "Brief description",
"changes": []string{
"Key change 1",
"Key change 2",
},
},
```
Focus on workflow-impacting changes agents need to know.
"""
[[steps]]
id = "bump-version-go"
title = "Bump version in version.go"
needs = ["update-info-go"]
description = """
Update the Go version constant.
```bash
# macOS
sed -i '' 's/Version = "[^"]*"/Version = "{{version}}"/' cmd/bd/version.go
# Linux
sed -i 's/Version = "[^"]*"/Version = "{{version}}"/' cmd/bd/version.go
```
Verify:
```bash
grep 'Version = ' cmd/bd/version.go
```
"""
[[steps]]
id = "bump-plugin-json"
title = "Bump version in plugin JSON files"
needs = ["bump-version-go"]
description = """
Update Claude plugin manifest versions.
```bash
# macOS/Linux with jq
jq '.version = "{{version}}"' .claude-plugin/plugin.json > tmp && mv tmp .claude-plugin/plugin.json
jq '.plugins[0].version = "{{version}}"' .claude-plugin/marketplace.json > tmp && mv tmp .claude-plugin/marketplace.json
```
Verify:
```bash
jq -r '.version' .claude-plugin/plugin.json
jq -r '.plugins[0].version' .claude-plugin/marketplace.json
```
"""
[[steps]]
id = "bump-mcp-python"
title = "Bump version in MCP Python package"
needs = ["bump-plugin-json"]
description = """
Update the beads-mcp Python package version.
```bash
# macOS
sed -i '' 's/version = "[^"]*"/version = "{{version}}"/' integrations/beads-mcp/pyproject.toml
sed -i '' 's/__version__ = "[^"]*"/__version__ = "{{version}}"/' integrations/beads-mcp/src/beads_mcp/__init__.py
# Linux
sed -i 's/version = "[^"]*"/version = "{{version}}"/' integrations/beads-mcp/pyproject.toml
sed -i 's/__version__ = "[^"]*"/__version__ = "{{version}}"/' integrations/beads-mcp/src/beads_mcp/__init__.py
```
Verify:
```bash
grep 'version = ' integrations/beads-mcp/pyproject.toml | head -1
grep '__version__ = ' integrations/beads-mcp/src/beads_mcp/__init__.py
```
"""
[[steps]]
id = "bump-npm-package"
title = "Bump version in npm package"
needs = ["bump-mcp-python"]
description = """
Update the npm package version.
```bash
# macOS/Linux with jq
jq '.version = "{{version}}"' npm-package/package.json > tmp && mv tmp npm-package/package.json
```
Verify:
```bash
jq -r '.version' npm-package/package.json
```
"""
[[steps]]
id = "bump-hook-templates"
title = "Bump version in hook templates"
needs = ["bump-npm-package"]
description = """
Update version comment in git hook templates.
```bash
# macOS
for f in cmd/bd/templates/hooks/pre-commit cmd/bd/templates/hooks/post-merge cmd/bd/templates/hooks/pre-push cmd/bd/templates/hooks/post-checkout; do
sed -i '' 's/# bd-hooks-version: .*/# bd-hooks-version: {{version}}/' "$f"
done
# Linux
for f in cmd/bd/templates/hooks/pre-commit cmd/bd/templates/hooks/post-merge cmd/bd/templates/hooks/pre-push cmd/bd/templates/hooks/post-checkout; do
sed -i 's/# bd-hooks-version: .*/# bd-hooks-version: {{version}}/' "$f"
done
```
Verify:
```bash
grep '# bd-hooks-version:' cmd/bd/templates/hooks/pre-commit
```
"""
[[steps]]
id = "bump-readme"
title = "Bump version in README badge"
needs = ["bump-hook-templates"]
description = """
Update the Alpha version badge in README.md.
```bash
# macOS
sed -i '' 's/Alpha (v[^)]*)/Alpha (v{{version}})/' README.md
# Linux
sed -i 's/Alpha (v[^)]*)/Alpha (v{{version}})/' README.md
```
Verify:
```bash
grep 'Alpha (v' README.md
```
"""
[[steps]]
id = "bump-default-nix"
title = "Bump version in default.nix"
needs = ["bump-readme"]
description = """
Update the Nix package version.
```bash
# macOS
sed -i '' 's/version = "[^"]*";/version = "{{version}}";/' default.nix
# Linux
sed -i 's/version = "[^"]*";/version = "{{version}}";/' default.nix
```
Verify:
```bash
grep 'version = ' default.nix
```
"""
[[steps]]
id = "update-vendorhash"
title = "Update Nix vendorHash if needed"
needs = ["bump-default-nix"]
description = """
Update vendorHash if go.mod/go.sum changed since last release.
Check if update is needed:
```bash
# Compare go.mod/go.sum against last tag
LAST_TAG=$(git describe --tags --abbrev=0)
git diff $LAST_TAG -- go.mod go.sum
```
If there are changes, run the update script:
```bash
./scripts/update-nix-vendorhash.sh
```
The script auto-detects nix or Docker and updates the hash automatically.
Verify:
```bash
grep 'vendorHash = ' default.nix
```
"""
[[steps]]
id = "stamp-changelog"
title = "Stamp changelog with release date"
needs = ["update-vendorhash"]
description = """
Add the release date to the [Unreleased] section header.
```bash
DATE=$(date +%Y-%m-%d)
# macOS
sed -i '' "s/## \\[Unreleased\\]/## [Unreleased]\\
\\
## [{{version}}] - $DATE/" CHANGELOG.md
# Linux
sed -i "s/## \\[Unreleased\\]/## [Unreleased]\\n\\n## [{{version}}] - $DATE/" CHANGELOG.md
```
Note: The update-changelog step handles the content; this step just adds the date stamp.
Verify:
```bash
grep -A2 '\\[Unreleased\\]' CHANGELOG.md
```
"""
[[steps]]
id = "verify-versions"
title = "Verify version consistency"
needs = ["stamp-changelog"]
description = """
Confirm all versions match {{version}}.
```bash
grep 'Version = ' cmd/bd/version.go
jq -r '.version' .claude-plugin/plugin.json
jq -r '.version' npm-package/package.json
grep 'version = ' integrations/beads-mcp/pyproject.toml
grep 'version = ' default.nix
```
All should show {{version}}.
"""
[[steps]]
id = "commit-release"
title = "Commit release"
needs = ["verify-versions"]
description = """
Stage and commit all version changes.
```bash
git add -A
git commit -m "chore: Bump version to {{version}}"
```
Review the commit to ensure all expected files are included.
"""
[[steps]]
id = "create-tag"
title = "Create release tag"
needs = ["commit-release"]
description = """
Create annotated git tag.
```bash
git tag -a v{{version}} -m "Release v{{version}}"
```
Verify: `git tag -l | tail -5`
"""
[[steps]]
id = "push-main"
title = "Push to main"
needs = ["create-tag"]
description = """
Push the release commit to origin.
```bash
git push origin main
```
If rejected, someone else pushed. Pull, rebase, try again.
"""
[[steps]]
id = "push-tag"
title = "Push release tag"
needs = ["push-main"]
description = """
Push the version tag to trigger CI release.
```bash
git push origin v{{version}}
```
This triggers GitHub Actions to build artifacts and publish.
**Phase 1 Complete**: After this step, signal phase-complete:
```bash
gt done --phase-complete
```
The gate will block until CI finishes. A new session will resume at Phase 2.
"""
# =============================================================================
# Gate: CI Completion
# =============================================================================
[[steps]]
id = "await-ci"
title = "Await CI: release.yml completion"
needs = ["push-tag"]
description = """
Gate step: Wait for GitHub Actions release workflow to complete.
This gate blocks until the release.yml workflow run succeeds.
The Refinery auto-discovers the workflow run triggered by v{{version}}
and closes this gate when it completes.
Expected time: 5-10 minutes
The gate monitors:
- Build artifacts (all platforms)
- Test suite pass
- npm publish
- PyPI publish
If the workflow fails, this gate remains open and requires manual intervention.
"""
[steps.gate]
type = "gh:run"
id = "release.yml"
timeout = "30m"
# =============================================================================
# Phase 2: Verification (Parallel)
# =============================================================================
[[steps]]
id = "verify-github"
title = "Verify GitHub release"
needs = ["await-ci"]
description = """
Check the GitHub releases page.
https://github.com/steveyegge/beads/releases/tag/v{{version}}
Verify:
- Release created
- Binaries attached (linux, darwin, windows)
- Checksums present
```bash
gh release view v{{version}} --json assets --jq '.assets[].name'
```
"""
[[steps]]
id = "verify-npm"
title = "Verify npm package"
needs = ["await-ci"]
description = """
Confirm npm package published.
```bash
npm show @beads/bd version
```
Should show {{version}}.
Also check: https://www.npmjs.com/package/@beads/bd
Note: npm registry may have a small propagation delay (1-2 min).
"""
[[steps]]
id = "verify-pypi"
title = "Verify PyPI package"
needs = ["await-ci"]
description = """
Confirm PyPI package published.
```bash
pip index versions beads-mcp 2>/dev/null | head -3
```
Or check: https://pypi.org/project/beads-mcp/
Should show {{version}}.
Note: PyPI may have a small propagation delay (1-2 min).
"""
# =============================================================================
# Phase 3: Installation (Fan-in)
# =============================================================================
[[steps]]
id = "local-install"
title = "Update local installation"
needs = ["verify-github", "verify-npm", "verify-pypi"]
description = """
Update local bd to the new version.
**Step 1: Install release binary**
Option A - Homebrew:
```bash
brew upgrade bd
```
Option B - Install script:
```bash
curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
```
**Step 2: Check for dev builds (developers only)**
If you build bd from source, you may have multiple `bd` binaries in your PATH:
```bash
which -a bd
```
Common locations:
- `~/.local/bin/bd` - release binary (installed above)
- `~/go/bin/bd` - Go dev build
- Project worktree builds
**If you have a dev build that takes precedence**, rebuild from source:
```bash
# From the beads source directory
make install
# Or using go install directly
go install -ldflags="-X main.Build=$(git rev-parse --short HEAD)" ./cmd/bd
```
**Verify ALL bd binaries are updated:**
```bash
# Check which bd runs by default
which bd && bd --version
# If you have go/bin/bd, verify it too
test -f ~/go/bin/bd && ~/go/bin/bd --version
```
All should show {{version}}.
"""
[[steps]]
id = "restart-daemons"
title = "Restart daemons"
needs = ["local-install"]
description = """
Restart bd daemons to pick up new version.
```bash
bd daemons killall
```
Daemons will auto-restart with new version on next bd command.
Verify:
```bash
bd daemons list
```
"""
[[steps]]
id = "release-complete"
title = "Release complete"
needs = ["restart-daemons"]
description = """
Release v{{version}} is complete!
Summary:
- All version files updated
- Git tag pushed
- CI artifacts built (via gate)
- npm and PyPI packages verified
- Local installation updated
- Daemons restarted
Optional next steps:
- Announce on social media
- Update documentation site
- Close related milestone
"""