| name | release-branch |
| model | haiku |
| description | Make the merge/PR/keep/discard decision for a feature branch, verify coverage gates, create the PR with gh, and clean up the worktree. Use when a feature is done and ready to ship, or when user says "release", "merge", or "open a PR". |
Release Branch
HARD GATE — Do NOT merge or release if tests fail or if coverage gates are not met. If the branch is red, return to develop-tdd to fix regressions or add missing tests before proceeding.
Finalize a completed feature branch: verify coverage gates, integrate onto main, and clean up the worktree.
Additional modes
--hotfix: Cherry-pick to main + tag. Skip PR in solo.
--squash-state: Squash chore(state): commits before merge.
Integrate mode
Read specs/state.yaml key workflow_mode (team-pr | solo-git). Fall back to profiles/solo-git.md.
| Mode | When | Ship path |
|---|
| solo-local | workflow_mode: solo-git | Auto: scripts/land-branch.sh if present, else fallback (Step 5) |
| team-pr | workflow_mode: team-pr (default) | gh pr create → gh pr merge --squash |
If unsure, prefer solo-local.
Process
Timing: bash scripts/bp-timing.sh start release-branch at invocation; bash scripts/bp-timing.sh end release-branch before handoff.
1. Final verification
<full test command> && <typecheck command> && <lint command>
git log main...HEAD --oneline | grep -vE "^[a-f0-9]+ (feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+$" && echo "❌ Non-conventional commits found" || echo "✅ Commits verified"
git log main...HEAD --format="%B" | grep -qiE 'co[- ]authored[- ]by' && echo "❌ Co-authored-by footer found — blocked" || echo "✅ No AI attribution"
2. Coverage check
2a. Security gate
If REVIEW.md is missing or stale → run security-review inline. Findings block the merge unless documented in EXCEPTIONS.md.
2b. Traceability gate
Run gate-trace before merge. FAIL blocks merge; CONCERNS requires explicit override in specs/state.yaml (traceability_override: CONCERNS accepted, reason: <explanation>). WAIVED if no matrix available.
Adversarial refute framing (e45s32): The final pre-merge check is refute, not rubber-stamp. Before declaring ready, actively try to disprove traceability completeness — missing story tags, absent verify evidence, stale security review. Only proceed when refutation fails.
3. Diff review
4. Decision
Options: Release (solo-local) / Open PR / Keep branch / Discard
5. Solo-local integrate
Run commit-message for the squash subject, then land:
bash scripts/land-branch.sh <task-slug> "feat(scope): description"
6. Create PR (team-pr only)
Create the pull request with a literal provenance marker in the body so agent-generated PRs are identifiable and do not rot silently:
<!-- bigpowers-provenance: agent-generated -->
Place the marker on its own line immediately after the ## Summary heading. Then merge via gh:
gh pr create --title "..." --body "$(cat <<'EOF'
## Summary
<!-- bigpowers-provenance: agent-generated -->
- ...
## Test plan
- [ ] ...
EOF
)"
gh pr merge --squash --delete-branch
semantic-release auto-detects the commit, bumps SemVer, tags the repo, generates release notes.
7a. Archive completed epic capsule
HARD GATE — When all epic stories are done (all done in execution-status.yaml), archive the capsule:
mv specs/epics/eNN-slug specs/epics/archive/
7b. CI verification & agent lock release (e39s02)
HARD GATE — Do NOT declare success until CI completes. Three-independent-facts (e45s15): commit landed, workflow green, registry visible — see REFERENCE.md.
bash scripts/wait-for-ci.sh --timeout 600 --interval 30
8. Clean up & return
Worktree prune, branch delete, git checkout main. Cycle-time: see REFERENCE.md.
Report: "Branch released."
Verify
→ verify: command -v gh >/dev/null 2>&1 && test -f specs/state.yaml && test -d skills/verify-work && echo "OK: release-branch dependencies available" || echo "FAIL"
Release Branch — Reference
Navigation
| Lines | Section |
|---|
| 1–3 | Title |
| 5–16 | Navigation |
| 18–41 | PR body template |
| 43–54 | Worktree cleanup |
| 56–96 | Cycle-time recording |
| 98–119 | CI verification |
| 121–126 | Solo-local fallback |
| 128–134 | Handoff |
| 136–155 | Reference block 1 (manual squash) |
Release Branch — Reference
PR body template (team-pr mode)
PR_TITLE="<type>(<scope>): <description>"
echo "$PR_TITLE" | grep -vE "^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+$" && echo "❌ ERROR: PR Title must follow Conventional Commits"
gh pr create \
--title "$PR_TITLE" \
--body "$(cat <<'EOF'
## Summary
- [What this PR does]
- [Key decisions made]
## Verify
- [ ] All tests pass
- [ ] Coverage gates met (≥80% overall, ≥95% business logic)
- [ ] CONVENTIONS.md compliance verified
- [ ] PR Title follows Conventional Commits (for automated release)
## specs/ artifacts
- [List any specs/ files produced or updated]
EOF
)"
Worktree cleanup details
git worktree prune
git worktree remove ../<branch-name> 2>/dev/null || true
git branch -d <branch-name>
If git worktree remove fails due to uncommitted changes, ask: "There are uncommitted changes in the worktree. Force remove? (y/n)". If yes: git worktree remove -f ../<branch-name>.
Cycle-time recording
After landing the branch, record delivery metrics using the git-derived,
additive script (replaces hand-arithmetic):
bash scripts/record-cycle-time.sh append \
--story <story_id> --bcps <bcps> \
--range "$(git merge-base main HEAD)..HEAD" \
--file specs/metrics/cycle-times.yaml
This appends a row to the cycle-times ledger with two separated metrics:
- effort_hours — ADDITIVE. Idle-stripped estimated effort from git commit
history (git-hours model: 120-min session threshold, 120-min first-commit pad).
Sums exactly to whole-repo effort. NO hand-arithmetic, NO wall-clock includes.
- lead_time_minutes — calendar latency from first commit to merge.
Median-aggregated across stories; NEVER summed.
The script also runs an additivity self-check: Σ(story effort) == whole-repo effort
within rounding tolerance.
Why not story_start minus story_end?
The previous hand-arithmetic approach (survey-context writes story_start,
release-branch writes story_end, agent hand-computes cycle_minutes) was
retired because:
- It was agent-self-reported — trivially fabricated or mis-subtracted.
- Wall-clock included overnight/weekend/UAT gaps — calendar latency,
not coding effort.
- The
bcp_per_hour metric was computationally meaningless (velocity
derived from a latency measurement).
The new approach derives effort from commit history (objective, reproducible)
and lead time from first commit → merge (honest calendar latency). See
docs/references/bcp.md for BCP sizing context and
scripts/record-cycle-time.sh for the full algorithm.
CI verification
The CI polling logic has been extracted to scripts/wait-for-ci.sh.
See the script's --help for usage. Step 7b of the main SKILL.md invokes it directly:
bash scripts/wait-for-ci.sh --timeout 600 --interval 30
Exit codes:
- 0 — all workflows green. Set
release.ci_verified: true in state.yaml.
- 1 — at least one workflow failed. Prints failure URLs. Set
handoff.next_skill = fix-bug.
- 2 — timeout. CI did not complete. Retry or investigate.
- 0 with warning —
gh CLI not available, git-only fallback confirmed push landed but CI status unverified.
The script handles: auto-discovery of all workflows for the current
branch/commit, polling until completion, success/failure/timeout exit codes,
and git-only fallback when gh CLI is unavailable.
Solo-local fallback detail
The fallback sequence (Path B above) handles the "remote has moved" case with git pull --rebase. Use when scripts/land-branch.sh is absent.
Acceptance: When fallback runs, main is updated, feature branch is deleted locally, and output states "used fallback merge (land-branch.sh not found)".
Handoff
Gate: READY -> next: survey-context
Writes: state.yaml handoff.next_skill = survey-context
Reference block 1
FEATURE_BRANCH=<task-slug>
DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo main)
if [ "$(git branch --show-current)" != "$FEATURE_BRANCH" ]; then
git checkout "$FEATURE_BRANCH"
fi
git checkout "$DEFAULT_BRANCH"
git pull --rebase origin "$DEFAULT_BRANCH" 2>/dev/null || git pull origin "$DEFAULT_BRANCH"
git merge --no-ff "$FEATURE_BRANCH" -m "<conventional-commit-message>"
git push origin "$DEFAULT_BRANCH"
git branch -d "$FEATURE_BRANCH"