End-to-end recipe for cutting a Forgeplan release. Follow these steps in
order — they reflect what every successful release in git log --oneline --all | grep release/v actually did, plus the two CLAUDE.md red lines
(#9 post-release sync, #10 dependabot triage at release time) we keep
forgetting and re-learning.
Audience: maintainers cutting a release. Not for one-off feature merges — those use the simpler
feat/* → devPR flow described ingit/.
A release ships when all of the following are true on dev:
cargo fmt -- --checkcleancargo check --workspace0 warningscargo clippy --workspace --all-targets -- -D warningscleancargo test --workspace --features test-helpers0 failures (1977+ tests as of v0.29.0)- Real E2E smoke on a fresh workspace covers the surfaces that changed in this minor (CLAUDE.md red line #5 — automated tests verify code correctness, not feature correctness)
forgeplan healthreports 0 blind spots, 0 stale, no advisory phase mismatches./scripts/check-mcp-tool-count.shexits 0 — no drift between MCP tool count in source and documentation (drift detector, v0.28.0+, PROB-050 A-30). If the release adds or removes MCP tools, update all documentation locations first, then run the script to verify.git log origin/main..origin/devshows a non-empty diff (no point releasing if dev hasn't diverged)
Full CI gate reference (what each check does and how to fix failures):
docs/operations/QUALITY-GATES.ru.md
Canonical playbook (v0.28.0+): marketplace/playbooks/release.yaml
кодифицирует pre-merge часть этого workflow как 12-step playbook
(preflight cargo gates → dependabot triage → CHANGELOG check → branch +
version bump → release PR creation → release-summary Note). На текущей
схеме SPEC-003 1.2 нет template engine, поэтому maintainer вручную
правит vX.Y.Z placeholder в step args перед playbook run release --yes
(tracked как PROB-050 A-1). Запуск playbook'а автоматизирует pipeline
ниже до шага 9; шаги 10+ (post-merge tag + sync PR) остаются
ручными.
If any of these fails, fix on dev (PR through feat/*) before
opening the release branch.
Run before opening the release branch:
gh api repos/ForgePlan/Forgeplan/dependabot/alerts --jq '.[] | select(.state == "open") | {number, severity: .security_advisory.severity, package: .dependency.package.name, summary: .security_advisory.summary}'For each open alert, classify into one of three buckets and record the choice in the release notes (Section 5):
- addressed —
cargo update -p <crate>ornpm audit fixresolves it in this release. Include the resolution commit SHA. - scheduled — fix planned for the next release. Reference the tracking issue or PRD.
- accepted-with-justification — risk accepted (e.g., dev-only dependency, no exploit path in our usage). One sentence of why.
Skipping this step is the failure mode that surfaced in PR #225 — alerts silently accumulate over multiple releases until a forced cleanup sprint.
Open a PR chore/changelog-vX.Y.Z from dev (or stack on whatever
last feature merged). The entry follows the loose Keep-a-Changelog
format already in CHANGELOG.md:
## [X.Y.Z] — YYYY-MM-DD — <one-line theme>
<2–3 sentence description of what this minor delivers>
### Added — <capability> (PRD-XXX / RFC-YYY / ADR-ZZZ)
- Bullet 1 (commit refs optional)
- Bullet 2
### Changed
- ...
### Fixed
- ...
### Migration notes
- ...Reference the active artifacts in section headings — readers should be able to jump from CHANGELOG to the PRD/RFC/ADR without searching.
Bump version = "X.Y.Z" in the workspace Cargo.toml. Run
cargo update --workspace so Cargo.lock reflects the new version
across all crates. Commit:
chore(release): bump workspace version to X.Y.Z
This commit lands on dev via the same chore/changelog-vX.Y.Z PR
(or its own chore/version-bump-vX.Y.Z PR — convention varies, just
make sure both land before opening the release branch).
git checkout dev && git pull
git checkout -b release/vX.Y.Z
git push -u origin release/vX.Y.Z
gh pr create --base main --head release/vX.Y.Z --title "release: vX.Y.Z" --body "$(cat <<'EOF'
## Summary
<copy CHANGELOG entry>
## Verification
- cargo test --workspace: NNNN passed / 0 failed
- cargo clippy: clean
- Real E2E smoke: <list surfaces verified>
- forgeplan health: clean
## Dependabot triage (red line #10)
- addressed: <PR/SHA references>
- scheduled: <next release>
- accepted-with-justification: <one-line reason>
EOF
)"Merge strategy: merge commit (NOT squash). Squash collapses the
release commit's history with all the dev commits accumulated since the
last release, losing per-commit attribution and breaking git bisect
for post-release regressions. This is CLAUDE.md red line #4 (DO NOT
push to a branch after a PR is merged — squash loses late commits)
applied to release branches specifically.
If the release branch needs an update from main during review (rare,
e.g., a hotfix on main mid-review), git merge origin/main into the
release branch — never rebase a release branch.
After the release PR merges:
git checkout main && git pull
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin vX.Y.ZThe tag triggers GitHub Actions (.github/workflows/release.yml,
configured by cargo-dist) which:
- Builds binaries for 5 platforms (aarch64/x86_64 × {apple-darwin, unknown-linux-gnu} + x86_64-pc-windows-msvc)
- Creates a GitHub release with all 28 artifacts (5 binaries × 2 archive formats × {sig, sha256}, plus tarball + checksums manifest)
- Updates the Homebrew formula in the
forgeplan-taprepo
Verify within 30 minutes:
gh release view vX.Y.Z --repo ForgePlan/Forgeplan
brew update && brew info forgeplan
forgeplan --version # should match X.Y.Z after `brew upgrade forgeplan`If cargo-dist fails, the most common cause is a flaky linker job on the
windows-msvc target — re-run via gh run rerun <run-id> --failed
before debugging.
This step is mandatory and the most-forgotten one. Branch protection
blocks direct push to dev, so without this PR the version bump and
tag commits on main never propagate back to dev. The next release
attempt then fights merge conflicts on Cargo.toml, and any feature
PR to dev opened in the gap shows phantom commits.
git checkout main && git pull
git checkout -b chore/sync-main-to-dev-after-vX.Y.Z
git merge --no-edit origin/dev || true # fast-forward when possible; resolve if needed
git push -u origin chore/sync-main-to-dev-after-vX.Y.Z
gh pr create --base dev --head chore/sync-main-to-dev-after-vX.Y.Z \
--title "chore: sync main to dev after vX.Y.Z" \
--body "Post-release sync per CLAUDE.md red line #9. Brings vX.Y.Z release commit + tag context back to dev so the next release branch starts from a consistent base."PR #223 (sync after v0.27.0) is the canonical example to copy.
Merge with merge commit (same reasoning as the release PR — preserve attribution).
After everything above:
memory_retainan observation: "Forgeplan vX.Y.Z released — " with entities tagged for the headline PRDs/RFCs.- Update Orchestra task statuses for any items in
Doing/Reviewthat this release shipped →Done, phaseDone. - If your CLAUDE.md or other docs reference
vN-1-specific behavior that's now changed, update them in the nextfeat/*PR (don't pile this onto the release).
For an urgent fix that can't wait for the next minor:
- Branch from
main(NOT dev):git checkout main && git pull && git checkout -b fix/<short-name> - Apply minimal fix + test
- PR to
maindirectly (skipdevfor urgency); labelhotfix - After merge: bump patch version on
main→ tag → release as sections 5–6 above - Backport to dev: open a
chore/backport-vX.Y.Z+1-to-devPR from main. Either cherry-pick the fix commit or merge the entire release/vX.Y.Z+1 ref into dev. Without this, dev silently regresses.
- Force-pushing release/ branches* — rewrites the merge commit hash GitHub Actions tagged. Result: cargo-dist runs against a SHA that doesn't exist in the published tag. Recover by re-tagging from the actual merge SHA.
- Squashing release/ PRs* — collapses N feature merges into a single
commit with the release subject;
git bisectbetween releases stops working. - Skipping the main → dev sync — see CLAUDE.md red line #9.
Symptom: next release branch has unexpected
Cargo.tomlconflicts. - Letting Dependabot alerts age across releases — see red line #10. Symptom: an emergency security audit interrupts feature work later.
- Tagging from
mainbefore the release PR merges — tag on a ghost SHA. Always wait for the merge to complete andgit pull mainbefore tagging.
# Pre-flight (on dev)
cargo fmt -- --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace
forgeplan health
gh api repos/ForgePlan/Forgeplan/dependabot/alerts --jq '.[] | select(.state=="open")'
# Version bump + CHANGELOG
$EDITOR Cargo.toml CHANGELOG.md
cargo update --workspace
git add Cargo.toml Cargo.lock CHANGELOG.md
git commit -m "chore(release): bump workspace version to X.Y.Z"
git push
# Release PR
git checkout -b release/vX.Y.Z dev
git push -u origin release/vX.Y.Z
gh pr create --base main --head release/vX.Y.Z --title "release: vX.Y.Z" --body "..."
# After release PR merges
git checkout main && git pull
git tag -a vX.Y.Z -m "Release vX.Y.Z" && git push origin vX.Y.Z
# Post-release sync (mandatory)
git checkout -b chore/sync-main-to-dev-after-vX.Y.Z main
git push -u origin chore/sync-main-to-dev-after-vX.Y.Z
gh pr create --base dev --head chore/sync-main-to-dev-after-vX.Y.Z --title "chore: sync main to dev after vX.Y.Z"