Configurable base/target branches, PR stacking, and the branchflow release-train plugin - #9
Open
UnityChaos wants to merge 5 commits into
Open
Configurable base/target branches, PR stacking, and the branchflow release-train plugin#9UnityChaos wants to merge 5 commits into
UnityChaos wants to merge 5 commits into
Conversation
…and PR stacking Abstract the hardcoded "branch from origin/main, PR to main" model so the plugins work for repos using develop/staging targets, semver update branches (major/minor/patch), and stacked feature branches. Fully backwards compatible: with no .claude-workflows.json and no new flags, behavior is byte-for-byte identical to before. - bin/_branch-lib.sh (triplicated, byte-identical): resolve_branches() reads a per-repo .claude-workflows.json and resolves target/base/prefix with precedence --onto > --target > <PLUGIN>_TARGET_BRANCH > --bump / semver:* label > config targetBranch > main. write/load_branch_meta persist the decision to <work-dir>/.branch-meta.json for --resume. - Orchestrators: new --target (all), --bump/--onto (issue-workflow); swap origin/main / --base main / divergence checks for $WF_TARGET/$WF_BASE_REF; banner shows target; stacked-parent-merged retarget in the inline trivial-integration path. - Skills: Step 0 target resolution (never re-type origin/main); doctrine reframed to "configured target, never a local branch"; stacking-aware PR base/body in plan; retarget-follow in review/integrate; gh pr calls made current-branch-relative so a custom branchPrefix works. - Hook: check-git-branch.sh reads protectedBranches from config, falling back to main/master/production. - Docs: root README, CLAUDE.md, and three plugin READMEs document the branching models. Versions: issue-workflow 1.5.0, deep-review 1.6.0, triage 1.1.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A fourth plugin that maintains and releases the branches the feature
plugins target -- the counterpart to their routing. Manages semver update
branches (major/minor/patch accumulators) or a single develop line:
cascades lower lines up into higher ones (keeping major ⊇ minor ⊇ patch)
and cuts version-bump release PRs into main. Merge-only and PR-driven --
never force-pushes, so it coexists with strict branch protection.
Commands (bin/branchflow, a subcommand CLI):
- init create the accumulator branches off the release branch
- status pending/behind (content-based), the invariant matrix, versions;
--check exits non-zero on a violated invariant (CI gate)
- cascade forward-merge a line into the next one up, via PRs; adjacent-only
so it chains cleanly under the eager Action; conflicts get a
Claude-resolved PR (or a plain PR when claude is unavailable)
- promote open a version-bump release PR into main; REFUSES (exit 3) if the
invariant is violated unless --force; --auto-merge lands it when
the env allows, else waits for manual merge
- reconcile tag a merged release, then merge main down into every accumulator
Design details:
- Containment is CONTENT-based (git cherry / patch-id), so per-branch reconcile
merge commits don't create false "ahead"/violation counts.
- Merges target PR numbers, not head branches (reconcile fronts several PRs
from main; a head-based merge would be ambiguous).
- Version is bumped only in the release PR (tag-based by default; optional
version file); the changelog skill drafts notes from the commit range.
- claude is a SOFT dependency (only conflict resolution + changelog use it),
so branchflow runs in CI; templates/branchflow.yml is an optional Action
that cascades on accumulator pushes and reconciles on main pushes.
- Conflict path aborts any in-progress merge and cleans up, never leaving the
working tree half-merged.
bin/_branchflow-lib.sh holds the config/version/invariant/merge primitives;
hooks are the byte-identical push-guard shared with the other plugins; the
plugin is registered in the marketplace and documented in its README plus the
root README/CLAUDE.md. Version 0.1.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
init now scaffolds and bootstraps a repo instead of assuming the config already exists (which its own docstring wrongly claimed it did). It reads the repo, prompts with smart defaults (--yes accepts all), and is idempotent. Steps: - Detect the default branch and a version file (package.json / Cargo.toml / pyproject.toml / ...). If no branching model is configured, prompt for trains-vs-single, branch names, release branch, tag prefix, version file, and changelog; merge the result into .claude-workflows.json (preserving any existing keys) and commit it to the release branch so the accumulators inherit it. - Create the accumulator branches off the release branch. - Offer a baseline version tag when versioning from tags and none exist. - With repo admin, set merge-commit-only (disable squash & rebase) + enable auto-merge, and require PRs on the release + accumulator branches via gh api -- the squash-disable being the setting that otherwise silently breaks the cascade. Skipped gracefully (with a warning) without admin or a remote. - Offer to drop in the eager-cascade Action. New interactive helpers (ask / ask_yn / bf_detect_version_file); version 0.2.0. Docs updated in the plugin README and root CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the two branching-model keywords user-facing and internally: 'trains' -> 'semver' (the major/minor/patch accumulator model) and 'single' -> 'develop' (the one-line model, default branch develop). Updates BF_MODE values, the init prompt/default, all mode comparisons, and the docs (plugin README + CLAUDE.md). No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The semver-accumulator model is single-version by nature, so it can't describe a monorepo where packages version independently. Add a delegation hook so branchflow owns the branch/PR/tag-push mechanics while a dedicated tool (changesets / nx / lerna / release-please) owns per-package versions. - New config: release.command (run by promote inside the release branch to bump per-package versions + changelogs) and release.tagCommand (run by reconcile to create per-package tags). - promote: when release.command is set, skip the built-in bump/changelog, run the command, commit whatever it changed (detected via git status, so new files count too), and open the release PR. In develop mode no bump arg is needed. Aborts cleanly if the command fails or produces no changes. - reconcile: when release.tagCommand is set, run it against the merged release in a throwaway worktree and push the tags (idempotent), instead of the single-tag path. - status: shows "Versioning: delegated to release.command (per-package)". - init: detects monorepos (workspaces / .changeset / nx.json / lerna.json), defaults to develop mode, and pre-fills the changesets commands. Parallel maintenance lines (patch 1.x while 2.0 develops) remain out of scope. Docs: README Example D + config rows; CLAUDE.md. Version 0.3.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Abstracts the plugins away from the hardcoded "branch from
origin/main, PR tomain" model so they work for repos with staging/develop targets and semver update branches, adds PR stacking, and introduces a newbranchflowrelease-train plugin that maintains and releases those branches.Fully backwards compatible: with no
.claude-workflows.jsonand no new flags, the existing plugins behave byte-for-byte as before.1. Configurable base/target branches (issue-workflow, deep-review, triage)
.claude-workflows.json(read from the target repo root) declares the target branch, an optionalmajor/minor/patchupdate-branch map, feature-branch prefix, and protected branches.bin/_branch-lib.shresolver (byte-identical per plugin) produces the base/target with precedence--onto>--target><PLUGIN>_TARGET_BRANCH>--bump/semver:*label > config >main, persisted to<work-dir>/.branch-meta.jsonso--resumereuses it.--target(all three),--bump major|minor|patchand--onto <branch|PR#>(issue-workflow).--ontocuts from and targets a parent feature branch so dependent PRs stack and land into the update branch once the feature is complete; integrate/review + the inline trivial-integration path follow GitHub's retarget when the parent PR merges.origin/main;gh prcalls are made current-branch-relative so a custombranchPrefixworks.check-git-branch.shhook now readsprotectedBranchesfrom config (falling back tomain master production).Versions: issue-workflow
1.5.0, deep-review1.6.0, triage1.1.0.2. New
branchflowrelease-train plugin (v0.3.0)A subcommand CLI — the counterpart to the feature plugins (they route PRs into lines; branchflow maintains and releases them). Merge-only and PR-driven, so it never force-pushes and coexists with strict branch protection.
claudeis a soft dependency, so it runs in CI.Two modes:
semver(major/minor/patch accumulators, invariantmajor ⊇ minor ⊇ patch) anddevelop(one line).init— interactive turnkey setup (smart, repo-detected defaults;--yesaccepts all): scaffolds the config, creates the accumulator branches, offers a baseline tag, and (with repo admin) sets merge-commit-only + branch protection via the GitHub API. Idempotent.status [--check]— content-based pending/behind, the invariant matrix, versions.--checkgates CI.cascade [<level>]— forward-merge a line into the next up (adjacent, chains under the eager Action); conflicts get a Claude-resolved PR.promote <level|bump>— version-bump release PR into main; refuses (exit 3) on a violated invariant unless--force;--auto-mergeor waits for manual merge.reconcile— tag the merged release, merge main down into every line.Monorepos: the semver-accumulator model is single-version by nature, so monorepos use
developmode + delegated versioning —release.command(run bypromote) andrelease.tagCommand(run byreconcile) hand per-package bumps/tags/changelogs to a dedicated tool (changesets/nx/lerna).initauto-detects monorepos and pre-fills the changesets commands. Parallel maintenance lines are out of scope.Correctness points hardened during testing: content-based containment (
git cherry/patch-id, so per-branch reconcile merges don't create false violations), merge-by-PR-number (reconcile fronts several PRs frommain), deterministicstatus --checkexit, and a conflict path that aborts cleanly and never leaves the tree half-merged.Testing
All shell
bash -n-clean; the two triplicated files (_branch-lib.sh,check-git-branch.sh) are byte-identical; all JSON manifests valid. The resolver, hook, and branchflow were exercised with local-repo integration tests (bare-origin + stubbedgh), including a full merge-simulating cascade→promote→reconcile lifecycle, delegated versioning, turnkeyinit --yes, and monorepo auto-detection.🤖 Generated with Claude Code